From 5542025079499968aa5afda7be9d073b069eb063 Mon Sep 17 00:00:00 2001 From: George Fu Date: Thu, 10 Nov 2022 19:47:03 -0500 Subject: [PATCH] feat(sso_credentials): if new sso session format in config, use sso token 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 --- .../next-release/feature-sso-7155c7f5.json | 5 + .gitignore | 1 + lib/credentials/sso_credentials.js | 163 ++++++++++++------ 3 files changed, 120 insertions(+), 49 deletions(-) create mode 100644 .changes/next-release/feature-sso-7155c7f5.json diff --git a/.changes/next-release/feature-sso-7155c7f5.json b/.changes/next-release/feature-sso-7155c7f5.json new file mode 100644 index 0000000000..f237f3b449 --- /dev/null +++ b/.changes/next-release/feature-sso-7155c7f5.json @@ -0,0 +1,5 @@ +{ + "type": "feature", + "category": "sso", + "description": "use sso token provider in sso credentials if sso session is set in config" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1e0693f248..bc942b1cd9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ test/browser/sample/appinfo.js dist/aws-sdk-all.js yarn.lock package-lock.json +workspace \ No newline at end of file diff --git a/lib/credentials/sso_credentials.js b/lib/credentials/sso_credentials.js index 0d983078b3..741bca0d4b 100644 --- a/lib/credentials/sso_credentials.js +++ b/lib/credentials/sso_credentials.js @@ -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`. @@ -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] || {}; @@ -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', @@ -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'), @@ -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); } },