sso_credentials.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. var AWS = require('../core');
  2. var path = require('path');
  3. var crypto = require('crypto');
  4. var iniLoader = AWS.util.iniLoader;
  5. /**
  6. * Represents credentials from sso.getRoleCredentials API for
  7. * `sso_*` values defined in shared credentials file.
  8. *
  9. * ## Using SSO credentials
  10. *
  11. * The credentials file must specify the information below to use sso:
  12. *
  13. * [profile sso-profile]
  14. * sso_account_id = 012345678901
  15. * sso_region = **-****-*
  16. * sso_role_name = SampleRole
  17. * sso_start_url = https://d-******.awsapps.com/start
  18. *
  19. * or using the session format:
  20. *
  21. * [profile sso-token]
  22. * sso_session = prod
  23. * sso_account_id = 012345678901
  24. * sso_role_name = SampleRole
  25. *
  26. * [sso-session prod]
  27. * sso_region = **-****-*
  28. * sso_start_url = https://d-******.awsapps.com/start
  29. *
  30. * This information will be automatically added to your shared credentials file by running
  31. * `aws configure sso`.
  32. *
  33. * ## Using custom profiles
  34. *
  35. * The SDK supports loading credentials for separate profiles. This can be done
  36. * in two ways:
  37. *
  38. * 1. Set the `AWS_PROFILE` environment variable in your process prior to
  39. * loading the SDK.
  40. * 2. Directly load the AWS.SsoCredentials provider:
  41. *
  42. * ```javascript
  43. * var creds = new AWS.SsoCredentials({profile: 'myprofile'});
  44. * AWS.config.credentials = creds;
  45. * ```
  46. *
  47. * @!macro nobrowser
  48. */
  49. AWS.SsoCredentials = AWS.util.inherit(AWS.Credentials, {
  50. /**
  51. * Creates a new SsoCredentials object.
  52. *
  53. * @param options [map] a set of options
  54. * @option options profile [String] (AWS_PROFILE env var or 'default')
  55. * the name of the profile to load.
  56. * @option options filename [String] ('~/.aws/credentials' or defined by
  57. * AWS_SHARED_CREDENTIALS_FILE process env var)
  58. * the filename to use when loading credentials.
  59. * @option options callback [Function] (err) Credentials are eagerly loaded
  60. * by the constructor. When the callback is called with no error, the
  61. * credentials have been loaded successfully.
  62. */
  63. constructor: function SsoCredentials(options) {
  64. AWS.Credentials.call(this);
  65. options = options || {};
  66. this.errorCode = 'SsoCredentialsProviderFailure';
  67. this.expired = true;
  68. this.filename = options.filename;
  69. this.profile = options.profile || process.env.AWS_PROFILE || AWS.util.defaultProfile;
  70. this.service = options.ssoClient;
  71. this.httpOptions = options.httpOptions || null;
  72. this.get(options.callback || AWS.util.fn.noop);
  73. },
  74. /**
  75. * @api private
  76. */
  77. load: function load(callback) {
  78. var self = this;
  79. try {
  80. var profiles = AWS.util.getProfilesFromSharedConfig(iniLoader, this.filename);
  81. var profile = profiles[this.profile] || {};
  82. if (Object.keys(profile).length === 0) {
  83. throw AWS.util.error(
  84. new Error('Profile ' + this.profile + ' not found'),
  85. { code: self.errorCode }
  86. );
  87. }
  88. if (profile.sso_session) {
  89. if (!profile.sso_account_id || !profile.sso_role_name) {
  90. throw AWS.util.error(
  91. new Error('Profile ' + this.profile + ' with session ' + profile.sso_session +
  92. ' does not have valid SSO credentials. Required parameters "sso_account_id", "sso_session", ' +
  93. '"sso_role_name". Reference: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html'),
  94. { code: self.errorCode }
  95. );
  96. }
  97. } else {
  98. if (!profile.sso_start_url || !profile.sso_account_id || !profile.sso_region || !profile.sso_role_name) {
  99. throw AWS.util.error(
  100. new Error('Profile ' + this.profile + ' does not have valid SSO credentials. Required parameters "sso_account_id", "sso_region", ' +
  101. '"sso_role_name", "sso_start_url". Reference: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html'),
  102. { code: self.errorCode }
  103. );
  104. }
  105. }
  106. this.getToken(this.profile, profile, function (err, token) {
  107. if (err) {
  108. return callback(err);
  109. }
  110. var request = {
  111. accessToken: token,
  112. accountId: profile.sso_account_id,
  113. roleName: profile.sso_role_name,
  114. };
  115. if (!self.service || self.service.config.region !== profile.sso_region) {
  116. self.service = new AWS.SSO({
  117. region: profile.sso_region,
  118. httpOptions: self.httpOptions,
  119. });
  120. }
  121. self.service.getRoleCredentials(request, function(err, data) {
  122. if (err || !data || !data.roleCredentials) {
  123. callback(AWS.util.error(
  124. err || new Error('Please log in using "aws sso login"'),
  125. { code: self.errorCode }
  126. ), null);
  127. } else if (!data.roleCredentials.accessKeyId || !data.roleCredentials.secretAccessKey || !data.roleCredentials.sessionToken || !data.roleCredentials.expiration) {
  128. throw AWS.util.error(new Error(
  129. 'SSO returns an invalid temporary credential.'
  130. ));
  131. } else {
  132. self.expired = false;
  133. self.accessKeyId = data.roleCredentials.accessKeyId;
  134. self.secretAccessKey = data.roleCredentials.secretAccessKey;
  135. self.sessionToken = data.roleCredentials.sessionToken;
  136. self.expireTime = new Date(data.roleCredentials.expiration);
  137. callback(null);
  138. }
  139. });
  140. });
  141. } catch (err) {
  142. callback(err);
  143. }
  144. },
  145. /**
  146. * @private
  147. * Uses legacy file system retrieval or if sso-session is set,
  148. * use the SSOTokenProvider.
  149. *
  150. * @param {string} profileName - name of the profile.
  151. * @param {object} profile - profile data containing sso_session or sso_start_url etc.
  152. * @param {function} callback - called with (err, (string) token).
  153. *
  154. * @returns {void}
  155. */
  156. getToken: function getToken(profileName, profile, callback) {
  157. var self = this;
  158. if (profile.sso_session) {
  159. var _iniLoader = AWS.util.iniLoader;
  160. var ssoSessions = _iniLoader.loadSsoSessionsFrom();
  161. var ssoSession = ssoSessions[profile.sso_session];
  162. Object.assign(profile, ssoSession);
  163. var ssoTokenProvider = new AWS.SSOTokenProvider({
  164. profile: profileName,
  165. });
  166. ssoTokenProvider.load(function (err) {
  167. if (err) {
  168. return callback(err);
  169. }
  170. return callback(null, ssoTokenProvider.token);
  171. });
  172. return;
  173. }
  174. try {
  175. /**
  176. * The time window (15 mins) that SDK will treat the SSO token expires in before the defined expiration date in token.
  177. * This is needed because server side may have invalidated the token before the defined expiration date.
  178. */
  179. var EXPIRE_WINDOW_MS = 15 * 60 * 1000;
  180. var hasher = crypto.createHash('sha1');
  181. var fileName = hasher.update(profile.sso_start_url).digest('hex') + '.json';
  182. var cachePath = path.join(
  183. iniLoader.getHomeDir(),
  184. '.aws',
  185. 'sso',
  186. 'cache',
  187. fileName
  188. );
  189. var cacheFile = AWS.util.readFileSync(cachePath);
  190. var cacheContent = null;
  191. if (cacheFile) {
  192. cacheContent = JSON.parse(cacheFile);
  193. }
  194. if (!cacheContent) {
  195. throw AWS.util.error(
  196. new Error('Cached credentials not found under ' + this.profile + ' profile. Please make sure you log in with aws sso login first'),
  197. { code: self.errorCode }
  198. );
  199. }
  200. if (!cacheContent.startUrl || !cacheContent.region || !cacheContent.accessToken || !cacheContent.expiresAt) {
  201. throw AWS.util.error(
  202. new Error('Cached credentials are missing required properties. Try running aws sso login.')
  203. );
  204. }
  205. if (new Date(cacheContent.expiresAt).getTime() - Date.now() <= EXPIRE_WINDOW_MS) {
  206. throw AWS.util.error(new Error(
  207. 'The SSO session associated with this profile has expired. To refresh this SSO session run aws sso login with the corresponding profile.'
  208. ));
  209. }
  210. return callback(null, cacheContent.accessToken);
  211. } catch (err) {
  212. return callback(err, null);
  213. }
  214. },
  215. /**
  216. * Loads the credentials from the AWS SSO process
  217. *
  218. * @callback callback function(err)
  219. * Called after the AWS SSO process has been executed. When this
  220. * callback is called with no error, it means that the credentials
  221. * information has been loaded into the object (as the `accessKeyId`,
  222. * `secretAccessKey`, and `sessionToken` properties).
  223. * @param err [Error] if an error occurred, this value will be filled
  224. * @see get
  225. */
  226. refresh: function refresh(callback) {
  227. iniLoader.clearCachedFiles();
  228. this.coalesceRefresh(callback || AWS.util.fn.callback);
  229. },
  230. });