248 lines
8.5 KiB
JavaScript
248 lines
8.5 KiB
JavaScript
var AWS = require('../core');
|
|
var path = require('path');
|
|
var crypto = require('crypto');
|
|
var iniLoader = AWS.util.iniLoader;
|
|
|
|
/**
|
|
* Represents credentials from sso.getRoleCredentials API for
|
|
* `sso_*` values defined in shared credentials file.
|
|
*
|
|
* ## Using SSO credentials
|
|
*
|
|
* The credentials file must specify the information below to use sso:
|
|
*
|
|
* [profile sso-profile]
|
|
* sso_account_id = 012345678901
|
|
* sso_region = **-****-*
|
|
* sso_role_name = SampleRole
|
|
* sso_start_url = https://d-******.awsapps.com/start
|
|
*
|
|
* or using the session format:
|
|
*
|
|
* [profile sso-token]
|
|
* sso_session = prod
|
|
* sso_account_id = 012345678901
|
|
* sso_role_name = SampleRole
|
|
*
|
|
* [sso-session prod]
|
|
* sso_region = **-****-*
|
|
* sso_start_url = https://d-******.awsapps.com/start
|
|
*
|
|
* This information will be automatically added to your shared credentials file by running
|
|
* `aws configure sso`.
|
|
*
|
|
* ## Using custom profiles
|
|
*
|
|
* The SDK supports loading credentials for separate profiles. This can be done
|
|
* in two ways:
|
|
*
|
|
* 1. Set the `AWS_PROFILE` environment variable in your process prior to
|
|
* loading the SDK.
|
|
* 2. Directly load the AWS.SsoCredentials provider:
|
|
*
|
|
* ```javascript
|
|
* var creds = new AWS.SsoCredentials({profile: 'myprofile'});
|
|
* AWS.config.credentials = creds;
|
|
* ```
|
|
*
|
|
* @!macro nobrowser
|
|
*/
|
|
AWS.SsoCredentials = AWS.util.inherit(AWS.Credentials, {
|
|
/**
|
|
* Creates a new SsoCredentials object.
|
|
*
|
|
* @param options [map] a set of options
|
|
* @option options profile [String] (AWS_PROFILE env var or 'default')
|
|
* the name of the profile to load.
|
|
* @option options filename [String] ('~/.aws/credentials' or defined by
|
|
* AWS_SHARED_CREDENTIALS_FILE process env var)
|
|
* the filename to use when loading credentials.
|
|
* @option options callback [Function] (err) Credentials are eagerly loaded
|
|
* by the constructor. When the callback is called with no error, the
|
|
* credentials have been loaded successfully.
|
|
*/
|
|
constructor: function SsoCredentials(options) {
|
|
AWS.Credentials.call(this);
|
|
|
|
options = options || {};
|
|
this.errorCode = 'SsoCredentialsProviderFailure';
|
|
this.expired = true;
|
|
|
|
this.filename = options.filename;
|
|
this.profile = options.profile || process.env.AWS_PROFILE || AWS.util.defaultProfile;
|
|
this.service = options.ssoClient;
|
|
this.httpOptions = options.httpOptions || null;
|
|
this.get(options.callback || AWS.util.fn.noop);
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
load: function load(callback) {
|
|
var self = this;
|
|
|
|
try {
|
|
var profiles = AWS.util.getProfilesFromSharedConfig(iniLoader, this.filename);
|
|
var profile = profiles[this.profile] || {};
|
|
|
|
if (Object.keys(profile).length === 0) {
|
|
throw AWS.util.error(
|
|
new Error('Profile ' + this.profile + ' not found'),
|
|
{ code: self.errorCode }
|
|
);
|
|
}
|
|
|
|
if (profile.sso_session) {
|
|
if (!profile.sso_account_id || !profile.sso_role_name) {
|
|
throw AWS.util.error(
|
|
new Error('Profile ' + this.profile + ' with session ' + profile.sso_session +
|
|
' does not have valid SSO credentials. Required parameters "sso_account_id", "sso_session", ' +
|
|
'"sso_role_name". Reference: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html'),
|
|
{ code: self.errorCode }
|
|
);
|
|
}
|
|
} else {
|
|
if (!profile.sso_start_url || !profile.sso_account_id || !profile.sso_region || !profile.sso_role_name) {
|
|
throw AWS.util.error(
|
|
new Error('Profile ' + this.profile + ' does not have valid SSO credentials. Required parameters "sso_account_id", "sso_region", ' +
|
|
'"sso_role_name", "sso_start_url". Reference: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html'),
|
|
{ code: self.errorCode }
|
|
);
|
|
}
|
|
}
|
|
|
|
this.getToken(this.profile, profile, function (err, token) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
var request = {
|
|
accessToken: token,
|
|
accountId: profile.sso_account_id,
|
|
roleName: profile.sso_role_name,
|
|
};
|
|
|
|
if (!self.service || self.service.config.region !== profile.sso_region) {
|
|
self.service = new AWS.SSO({
|
|
region: profile.sso_region,
|
|
httpOptions: self.httpOptions,
|
|
});
|
|
}
|
|
|
|
self.service.getRoleCredentials(request, function(err, data) {
|
|
if (err || !data || !data.roleCredentials) {
|
|
callback(AWS.util.error(
|
|
err || new Error('Please log in using "aws sso login"'),
|
|
{ code: self.errorCode }
|
|
), null);
|
|
} else if (!data.roleCredentials.accessKeyId || !data.roleCredentials.secretAccessKey || !data.roleCredentials.sessionToken || !data.roleCredentials.expiration) {
|
|
throw AWS.util.error(new Error(
|
|
'SSO returns an invalid temporary credential.'
|
|
));
|
|
} else {
|
|
self.expired = false;
|
|
self.accessKeyId = data.roleCredentials.accessKeyId;
|
|
self.secretAccessKey = data.roleCredentials.secretAccessKey;
|
|
self.sessionToken = data.roleCredentials.sessionToken;
|
|
self.expireTime = new Date(data.roleCredentials.expiration);
|
|
callback(null);
|
|
}
|
|
});
|
|
});
|
|
} catch (err) {
|
|
callback(err);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* Uses legacy file system retrieval or if sso-session is set,
|
|
* use the SSOTokenProvider.
|
|
*
|
|
* @param {string} profileName - name of the profile.
|
|
* @param {object} profile - profile data containing sso_session or sso_start_url etc.
|
|
* @param {function} callback - called with (err, (string) token).
|
|
*
|
|
* @returns {void}
|
|
*/
|
|
getToken: function getToken(profileName, profile, callback) {
|
|
var self = this;
|
|
|
|
if (profile.sso_session) {
|
|
var _iniLoader = AWS.util.iniLoader;
|
|
var ssoSessions = _iniLoader.loadSsoSessionsFrom();
|
|
var ssoSession = ssoSessions[profile.sso_session];
|
|
Object.assign(profile, ssoSession);
|
|
|
|
var ssoTokenProvider = new AWS.SSOTokenProvider({
|
|
profile: profileName,
|
|
});
|
|
ssoTokenProvider.get(function (err) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
return callback(null, ssoTokenProvider.token);
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
/**
|
|
* The time window (15 mins) that SDK will treat the SSO token expires in before the defined expiration date in token.
|
|
* This is needed because server side may have invalidated the token before the defined expiration date.
|
|
*/
|
|
var EXPIRE_WINDOW_MS = 15 * 60 * 1000;
|
|
var hasher = crypto.createHash('sha1');
|
|
var fileName = hasher.update(profile.sso_start_url).digest('hex') + '.json';
|
|
var cachePath = path.join(
|
|
iniLoader.getHomeDir(),
|
|
'.aws',
|
|
'sso',
|
|
'cache',
|
|
fileName
|
|
);
|
|
var cacheFile = AWS.util.readFileSync(cachePath);
|
|
var cacheContent = null;
|
|
if (cacheFile) {
|
|
cacheContent = JSON.parse(cacheFile);
|
|
}
|
|
if (!cacheContent) {
|
|
throw AWS.util.error(
|
|
new Error('Cached credentials not found under ' + this.profile + ' profile. Please make sure you log in with aws sso login first'),
|
|
{ code: self.errorCode }
|
|
);
|
|
}
|
|
|
|
if (!cacheContent.startUrl || !cacheContent.region || !cacheContent.accessToken || !cacheContent.expiresAt) {
|
|
throw AWS.util.error(
|
|
new Error('Cached credentials are missing required properties. Try running aws sso login.')
|
|
);
|
|
}
|
|
|
|
if (new Date(cacheContent.expiresAt).getTime() - Date.now() <= EXPIRE_WINDOW_MS) {
|
|
throw AWS.util.error(new Error(
|
|
'The SSO session associated with this profile has expired. To refresh this SSO session run aws sso login with the corresponding profile.'
|
|
));
|
|
}
|
|
|
|
return callback(null, cacheContent.accessToken);
|
|
} catch (err) {
|
|
return callback(err, null);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Loads the credentials from the AWS SSO process
|
|
*
|
|
* @callback callback function(err)
|
|
* Called after the AWS SSO process has been executed. When this
|
|
* callback is called with no error, it means that the credentials
|
|
* information has been loaded into the object (as the `accessKeyId`,
|
|
* `secretAccessKey`, and `sessionToken` properties).
|
|
* @param err [Error] if an error occurred, this value will be filled
|
|
* @see get
|
|
*/
|
|
refresh: function refresh(callback) {
|
|
iniLoader.clearCachedFiles();
|
|
this.coalesceRefresh(callback || AWS.util.fn.callback);
|
|
},
|
|
});
|