Skip to content

Commit

Permalink
feat(sso_credentials): if new sso session format in config, use sso t…
Browse files Browse the repository at this point in the history
…oken provider in sso credentials (#4267)

* feat(sso_credentials): if new sso session format in config, use sso token provider in sso credentials

* feat(sso_credentials): fix unit test for sso

* feat(sso_credentials): remove hard coded region from example
  • Loading branch information
kuhe authored Nov 11, 2022
1 parent f0463d1 commit 5542025
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 49 deletions.
5 changes: 5 additions & 0 deletions .changes/next-release/feature-sso-7155c7f5.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "feature",
"category": "sso",
"description": "use sso token provider in sso credentials if sso session is set in config"
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ test/browser/sample/appinfo.js
dist/aws-sdk-all.js
yarn.lock
package-lock.json
workspace
163 changes: 114 additions & 49 deletions lib/credentials/sso_credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,22 @@ var iniLoader = AWS.util.iniLoader;
*
* The credentials file must specify the information below to use sso:
*
* [default]
* [profile sso-profile]
* sso_account_id = 012345678901
* sso_region = us-east-1
* sso_region = **-****-*
* sso_role_name = SampleRole
* sso_start_url = https://d-abc123.awsapps.com/start
* 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`.
Expand Down Expand Up @@ -68,14 +79,8 @@ AWS.SsoCredentials = AWS.util.inherit(AWS.Credentials, {
* @api private
*/
load: function load(callback) {
/**
* 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.
*
* @internal
*/
var EXPIRE_WINDOW_MS = 15 * 60 * 1000;
var self = this;

try {
var profiles = AWS.util.getProfilesFromSharedConfig(iniLoader, this.filename);
var profile = profiles[this.profile] || {};
Expand All @@ -87,17 +92,107 @@ AWS.SsoCredentials = AWS.util.inherit(AWS.Credentials, {
);
}

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 }
);
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.load(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',
Expand All @@ -110,7 +205,6 @@ AWS.SsoCredentials = AWS.util.inherit(AWS.Credentials, {
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'),
Expand All @@ -130,38 +224,9 @@ AWS.SsoCredentials = AWS.util.inherit(AWS.Credentials, {
));
}

if (!self.service || self.service.config.region !== profile.sso_region) {
self.service = new AWS.SSO({
region: profile.sso_region,
httpOptions: this.httpOptions,
});
}
var request = {
accessToken: cacheContent.accessToken,
accountId: profile.sso_account_id,
roleName: profile.sso_role_name,
};
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);
}
});
return callback(null, cacheContent.accessToken);
} catch (err) {
callback(err);
return callback(err, null);
}
},

Expand Down

0 comments on commit 5542025

Please sign in to comment.