From 6d7229130fbf2c4509b6075777f916d510406acc Mon Sep 17 00:00:00 2001 From: Leo Liu Date: Wed, 30 Aug 2023 18:57:34 +0800 Subject: [PATCH 1/9] implement --- .../shared_ini_file_credentials.js | 90 ++++++++++--------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/lib/credentials/shared_ini_file_credentials.js b/lib/credentials/shared_ini_file_credentials.js index 47290be6e6..f8fd189251 100644 --- a/lib/credentials/shared_ini_file_credentials.js +++ b/lib/credentials/shared_ini_file_credentials.js @@ -232,53 +232,61 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, { ); } - var sourceCredentials = new AWS.SharedIniFileCredentials( - AWS.util.merge(this.options || {}, { - profile: sourceProfileName, - preferStaticCredentials: true - }) - ); - - this.roleArn = roleArn; - var sts = new STS({ - credentials: sourceCredentials, - region: profileRegion, - httpOptions: this.httpOptions + var sourceOptions = AWS.util.merge(this.options || {}, { + profile: sourceProfileName, + preferStaticCredentials: true }); + var chain = new AWS.CredentialProviderChain([ + function () { return new AWS.SsoCredentials(sourceOptions); }, + function () { return new AWS.SharedIniFileCredentials(sourceOptions); }, + function () { return new AWS.ProcessCredentials(sourceOptions); }, + function () { return new AWS.TokenFileWebIdentityCredentials(sourceOptions); }, + ]); + chain.resolve(function (err, creds) { + if (err) throw err; + else { + this.roleArn = roleArn; + var sts = new STS({ + credentials: creds, + region: profileRegion, + httpOptions: this.httpOptions + }); - var roleParams = { - DurationSeconds: durationSeconds, - RoleArn: roleArn, - RoleSessionName: roleSessionName || 'aws-sdk-js-' + Date.now() - }; + var roleParams = { + DurationSeconds: durationSeconds, + RoleArn: roleArn, + RoleSessionName: roleSessionName || 'aws-sdk-js-' + Date.now() + }; - if (externalId) { - roleParams.ExternalId = externalId; - } + if (externalId) { + roleParams.ExternalId = externalId; + } - if (mfaSerial && self.tokenCodeFn) { - roleParams.SerialNumber = mfaSerial; - self.tokenCodeFn(mfaSerial, function(err, token) { - if (err) { - var message; - if (err instanceof Error) { - message = err.message; - } else { - message = err; - } - callback( - AWS.util.error( - new Error('Error fetching MFA token: ' + message), - { code: 'SharedIniFileCredentialsProviderFailure' } - )); + if (mfaSerial && self.tokenCodeFn) { + roleParams.SerialNumber = mfaSerial; + self.tokenCodeFn(mfaSerial, function(err, token) { + if (err) { + var message; + if (err instanceof Error) { + message = err.message; + } else { + message = err; + } + callback( + AWS.util.error( + new Error('Error fetching MFA token: ' + message), + { code: 'SharedIniFileCredentialsProviderFailure' } + )); + return; + } + + roleParams.TokenCode = token; + sts.assumeRole(roleParams, callback); + }); return; } - - roleParams.TokenCode = token; sts.assumeRole(roleParams, callback); - }); - return; - } - sts.assumeRole(roleParams, callback); + } + }); } }); From 54fead091ecfbd2ea3790f0152514ca255938b63 Mon Sep 17 00:00:00 2001 From: Leo Liu Date: Wed, 30 Aug 2023 20:54:52 +0800 Subject: [PATCH 2/9] add test --- test/credentials.spec.js | 49 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/credentials.spec.js b/test/credentials.spec.js index 6d290c6c28..863d088bcf 100644 --- a/test/credentials.spec.js +++ b/test/credentials.spec.js @@ -1324,6 +1324,55 @@ const exp = require('constants'); }); }); + it('will use credential_process in the base profile', function(done) { + var mock = [ + '[default]', + 'role_arn = arn', + 'source_profile = base', + '[base]', + 'credential_process=federated_cli_mock', + ].join('\n'); + helpers.spyOn(AWS.util, 'readFileSync').andReturn(mock); + + var child_process = require('child_process'); + mockProcess = '{"Version": 1,"AccessKeyId": "akid","SecretAccessKey": "secret","SessionToken": "session","Expiration": ""}'; + helpers.spyOn(child_process, 'exec').andCallFake(function (_, _, cb) { + cb(undefined, mockProcess, undefined); + }); + + var expiration = new Date(Date.now() + 1000); + var STSPrototype = (new STS()).constructor.prototype; + var spy = helpers.spyOn(STSPrototype, 'assumeRole').andCallFake( + function(roleParams, callback) { + AWS.util.defer(function () { + callback(null, { + Credentials: { + AccessKeyId: 'KEY', + SecretAccessKey: 'SECRET', + SessionToken: 'TOKEN', + Expiration: expiration + } + }); + }); + } + ); + + var creds = new AWS.SharedIniFileCredentials(); + expect(creds.roleArn).to.equal('arn'); + return creds.refresh(function(err) { + expect(spy.calls[0].arguments[0].RoleArn).to.equal('arn'); + expect(spy.calls[0].object.config.credentials.profile).to.equal('base'); + expect(spy.calls[0].object.config.credentials.accessKeyId).to.equal('akid'); + expect(spy.calls[0].object.config.credentials.secretAccessKey).to.equal('secret'); + expect(spy.calls[0].object.config.credentials.sessionToken).to.equal('session'); + expect(creds.accessKeyId).to.equal('KEY'); + expect(creds.secretAccessKey).to.equal('SECRET'); + expect(creds.sessionToken).to.equal('TOKEN'); + expect(creds.expireTime).to.equal(expiration); + return done(); + }); + }); + describe('mfa serial callback', function() { beforeEach(function() { From 5c6a0ea2c0bfc9839aea74b3ac7b868c4ca1ea74 Mon Sep 17 00:00:00 2001 From: Leo Liu Date: Wed, 30 Aug 2023 21:33:08 +0800 Subject: [PATCH 3/9] verify credential --- test/credentials.spec.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/test/credentials.spec.js b/test/credentials.spec.js index 863d088bcf..19ea5c72cd 100644 --- a/test/credentials.spec.js +++ b/test/credentials.spec.js @@ -1344,15 +1344,22 @@ const exp = require('constants'); var STSPrototype = (new STS()).constructor.prototype; var spy = helpers.spyOn(STSPrototype, 'assumeRole').andCallFake( function(roleParams, callback) { - AWS.util.defer(function () { - callback(null, { - Credentials: { - AccessKeyId: 'KEY', - SecretAccessKey: 'SECRET', - SessionToken: 'TOKEN', - Expiration: expiration - } + if (this.config.credentials.accessKeyId === 'akid' && + this.config.credentials.secretAccessKey === 'secret' && + this.config.credentials.sessionToken === 'session') { + setImmediate(function () { + callback(null, { + Credentials: { + AccessKeyId: 'KEY', + SecretAccessKey: 'SECRET', + SessionToken: 'TOKEN', + Expiration: expiration + } + }); }); + } + setImmediate(function () { + callback(new Error('INVALID CREDENTIAL')); }); } ); From b9e893837c1ff2cc8135b06c0bcb2aee35860c45 Mon Sep 17 00:00:00 2001 From: Leo Liu Date: Wed, 30 Aug 2023 22:47:26 +0800 Subject: [PATCH 4/9] update test case --- test/credentials.spec.js | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/test/credentials.spec.js b/test/credentials.spec.js index 19ea5c72cd..b386d4837d 100644 --- a/test/credentials.spec.js +++ b/test/credentials.spec.js @@ -1364,19 +1364,26 @@ const exp = require('constants'); } ); - var creds = new AWS.SharedIniFileCredentials(); - expect(creds.roleArn).to.equal('arn'); - return creds.refresh(function(err) { - expect(spy.calls[0].arguments[0].RoleArn).to.equal('arn'); - expect(spy.calls[0].object.config.credentials.profile).to.equal('base'); - expect(spy.calls[0].object.config.credentials.accessKeyId).to.equal('akid'); - expect(spy.calls[0].object.config.credentials.secretAccessKey).to.equal('secret'); - expect(spy.calls[0].object.config.credentials.sessionToken).to.equal('session'); - expect(creds.accessKeyId).to.equal('KEY'); - expect(creds.secretAccessKey).to.equal('SECRET'); - expect(creds.sessionToken).to.equal('TOKEN'); - expect(creds.expireTime).to.equal(expiration); - return done(); + var creds = new AWS.SharedIniFileCredentials({ + callback: function (err) { + expect(err).to.be.null; + expect(spy.calls.length).to.equal(2); + expect(spy.calls[0].arguments[0].RoleArn).to.equal('arn'); + expect(spy.calls[0].object.config.credentials.profile).to.equal('base'); + expect(spy.calls[0].object.config.credentials.accessKeyId).to.be.undefined; + expect(spy.calls[0].object.config.credentials.secretAccessKey).to.be.undefined; + expect(spy.calls[0].object.config.credentials.sessionToken).to.be.undefined; + expect(spy.calls[1].arguments[0].RoleArn).to.equal('arn'); + expect(spy.calls[1].object.config.credentials.profile).to.equal('base'); + expect(spy.calls[1].object.config.credentials.accessKeyId).to.equal('akid'); + expect(spy.calls[1].object.config.credentials.secretAccessKey).to.equal('secret'); + expect(spy.calls[1].object.config.credentials.sessionToken).to.equal('session'); + expect(creds.accessKeyId).to.equal('KEY'); + expect(creds.secretAccessKey).to.equal('SECRET'); + expect(creds.sessionToken).to.equal('TOKEN'); + expect(creds.expireTime).to.equal(expiration); + done(); + } }); }); From b792b3b2702f8b8bfda4c0ef0828b2559351656a Mon Sep 17 00:00:00 2001 From: Leo Liu Date: Wed, 30 Aug 2023 22:47:52 +0800 Subject: [PATCH 5/9] reimplement --- .../shared_ini_file_credentials.js | 132 ++++++++++-------- 1 file changed, 71 insertions(+), 61 deletions(-) diff --git a/lib/credentials/shared_ini_file_credentials.js b/lib/credentials/shared_ini_file_credentials.js index f8fd189251..ecc70b7b28 100644 --- a/lib/credentials/shared_ini_file_credentials.js +++ b/lib/credentials/shared_ini_file_credentials.js @@ -122,18 +122,36 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, { ); if (profile['role_arn'] && !preferStaticCredentialsToRoleArn) { - this.loadRoleProfile(profiles, profile, function(err, data) { - if (err) { - callback(err); - } else { - self.expired = false; - self.accessKeyId = data.Credentials.AccessKeyId; - self.secretAccessKey = data.Credentials.SecretAccessKey; - self.sessionToken = data.Credentials.SessionToken; - self.expireTime = data.Credentials.Expiration; - callback(null); + var sourceProfileName = profile['source_profile']; + var errs = []; + for (var [credentials, skip] of [ + [AWS.SharedIniFileCredentials, function () { return false; }], + [AWS.ProcessCredentials, function () { + return !profiles[sourceProfileName] || + !profiles[sourceProfileName]['credential_process']; + }], + ]) { + if (skip()){ + continue + } + this.loadRoleProfile(profiles, profile, credentials, function(err, data) { + if (err) { + errs.push(err); + } else { + self.expired = false; + self.accessKeyId = data.Credentials.AccessKeyId; + self.secretAccessKey = data.Credentials.SecretAccessKey; + self.sessionToken = data.Credentials.SessionToken; + self.expireTime = data.Credentials.Expiration; + callback(null); + sourceProfileName = ''; + } + }); + if (!sourceProfileName) { + return; } - }); + } + errs.forEach(function (err) { callback(err); }); return; } @@ -176,7 +194,7 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, { /** * @api private */ - loadRoleProfile: function loadRoleProfile(creds, roleProfile, callback) { + loadRoleProfile: function loadRoleProfile(creds, roleProfile, credentials, callback) { if (this.disableAssumeRole) { throw AWS.util.error( new Error('Role assumption profiles are disabled. ' + @@ -232,61 +250,53 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, { ); } - var sourceOptions = AWS.util.merge(this.options || {}, { - profile: sourceProfileName, - preferStaticCredentials: true - }); - var chain = new AWS.CredentialProviderChain([ - function () { return new AWS.SsoCredentials(sourceOptions); }, - function () { return new AWS.SharedIniFileCredentials(sourceOptions); }, - function () { return new AWS.ProcessCredentials(sourceOptions); }, - function () { return new AWS.TokenFileWebIdentityCredentials(sourceOptions); }, - ]); - chain.resolve(function (err, creds) { - if (err) throw err; - else { - this.roleArn = roleArn; - var sts = new STS({ - credentials: creds, - region: profileRegion, - httpOptions: this.httpOptions - }); + var sourceCredentials = new credentials( + AWS.util.merge(this.options || {}, { + profile: sourceProfileName, + preferStaticCredentials: true + }) + ); - var roleParams = { - DurationSeconds: durationSeconds, - RoleArn: roleArn, - RoleSessionName: roleSessionName || 'aws-sdk-js-' + Date.now() - }; + this.roleArn = roleArn; + var sts = new STS({ + credentials: sourceCredentials, + region: profileRegion, + httpOptions: this.httpOptions + }); - if (externalId) { - roleParams.ExternalId = externalId; - } + var roleParams = { + DurationSeconds: durationSeconds, + RoleArn: roleArn, + RoleSessionName: roleSessionName || 'aws-sdk-js-' + Date.now() + }; - if (mfaSerial && self.tokenCodeFn) { - roleParams.SerialNumber = mfaSerial; - self.tokenCodeFn(mfaSerial, function(err, token) { - if (err) { - var message; - if (err instanceof Error) { - message = err.message; - } else { - message = err; - } - callback( - AWS.util.error( - new Error('Error fetching MFA token: ' + message), - { code: 'SharedIniFileCredentialsProviderFailure' } - )); - return; - } + if (externalId) { + roleParams.ExternalId = externalId; + } - roleParams.TokenCode = token; - sts.assumeRole(roleParams, callback); - }); + if (mfaSerial && self.tokenCodeFn) { + roleParams.SerialNumber = mfaSerial; + self.tokenCodeFn(mfaSerial, function(err, token) { + if (err) { + var message; + if (err instanceof Error) { + message = err.message; + } else { + message = err; + } + callback( + AWS.util.error( + new Error('Error fetching MFA token: ' + message), + { code: 'SharedIniFileCredentialsProviderFailure' } + )); return; } + + roleParams.TokenCode = token; sts.assumeRole(roleParams, callback); - } - }); + }); + return; + } + sts.assumeRole(roleParams, callback); } }); From c86fdf9f3f7ba77e73043b7b9dccde96a3fb23bb Mon Sep 17 00:00:00 2001 From: Leo Liu Date: Thu, 31 Aug 2023 01:08:20 +0800 Subject: [PATCH 6/9] refine --- test/credentials.spec.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/test/credentials.spec.js b/test/credentials.spec.js index b386d4837d..4a6de9224b 100644 --- a/test/credentials.spec.js +++ b/test/credentials.spec.js @@ -1347,20 +1347,16 @@ const exp = require('constants'); if (this.config.credentials.accessKeyId === 'akid' && this.config.credentials.secretAccessKey === 'secret' && this.config.credentials.sessionToken === 'session') { - setImmediate(function () { - callback(null, { - Credentials: { - AccessKeyId: 'KEY', - SecretAccessKey: 'SECRET', - SessionToken: 'TOKEN', - Expiration: expiration - } - }); + callback(null, { + Credentials: { + AccessKeyId: 'KEY', + SecretAccessKey: 'SECRET', + SessionToken: 'TOKEN', + Expiration: expiration + } }); } - setImmediate(function () { - callback(new Error('INVALID CREDENTIAL')); - }); + callback(new Error('INVALID CREDENTIAL')); } ); From 0fdaf522425cfc72c0b909dc24bcfad2dda15af9 Mon Sep 17 00:00:00 2001 From: Leo Liu Date: Thu, 31 Aug 2023 01:35:06 +0800 Subject: [PATCH 7/9] fix callback --- .../shared_ini_file_credentials.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/credentials/shared_ini_file_credentials.js b/lib/credentials/shared_ini_file_credentials.js index ecc70b7b28..2e091e1f4f 100644 --- a/lib/credentials/shared_ini_file_credentials.js +++ b/lib/credentials/shared_ini_file_credentials.js @@ -123,18 +123,22 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, { if (profile['role_arn'] && !preferStaticCredentialsToRoleArn) { var sourceProfileName = profile['source_profile']; - var errs = []; - for (var [credentials, skip] of [ + var credentialsChain = [ [AWS.SharedIniFileCredentials, function () { return false; }], [AWS.ProcessCredentials, function () { return !profiles[sourceProfileName] || !profiles[sourceProfileName]['credential_process']; }], - ]) { + ]; + var i = credentialsChain.length; + var errs = []; + for (var [credentials, skip] of credentialsChain) { if (skip()){ + i--; continue } this.loadRoleProfile(profiles, profile, credentials, function(err, data) { + i--; if (err) { errs.push(err); } else { @@ -151,7 +155,14 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, { return; } } - errs.forEach(function (err) { callback(err); }); + (function() { + var j = setInterval(function() { + if (i <= 0) { + errs.forEach(function(err) { callback(err); }); + clearInterval(j); + } + }, 0); + })(); return; } From f17268e9f129a0695f4f5b984f3a3959106a0724 Mon Sep 17 00:00:00 2001 From: Leo Liu Date: Thu, 31 Aug 2023 01:49:31 +0800 Subject: [PATCH 8/9] lint --- lib/credentials/shared_ini_file_credentials.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/credentials/shared_ini_file_credentials.js b/lib/credentials/shared_ini_file_credentials.js index 2e091e1f4f..1c0629ceac 100644 --- a/lib/credentials/shared_ini_file_credentials.js +++ b/lib/credentials/shared_ini_file_credentials.js @@ -123,19 +123,20 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, { if (profile['role_arn'] && !preferStaticCredentialsToRoleArn) { var sourceProfileName = profile['source_profile']; - var credentialsChain = [ - [AWS.SharedIniFileCredentials, function () { return false; }], - [AWS.ProcessCredentials, function () { + var credentialsSetChain = [ + [AWS.SharedIniFileCredentials, function() { return false; }], + [AWS.ProcessCredentials, function() { return !profiles[sourceProfileName] || !profiles[sourceProfileName]['credential_process']; }], ]; - var i = credentialsChain.length; + var i = credentialsSetChain.length; var errs = []; - for (var [credentials, skip] of credentialsChain) { - if (skip()){ + for (var index = 0; index < credentialsSetChain.length; index++) { + var credentials = credentialsSetChain[index][0], skip = credentialsSetChain[index][1]; + if (skip()) { i--; - continue + continue; } this.loadRoleProfile(profiles, profile, credentials, function(err, data) { i--; From 385e3ae699b21c50ca3183234087ee7ef49cf737 Mon Sep 17 00:00:00 2001 From: Leo Liu Date: Thu, 31 Aug 2023 01:52:47 +0800 Subject: [PATCH 9/9] add change log --- .changes/next-release/bugfix-Credentials-6b971f33.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changes/next-release/bugfix-Credentials-6b971f33.json diff --git a/.changes/next-release/bugfix-Credentials-6b971f33.json b/.changes/next-release/bugfix-Credentials-6b971f33.json new file mode 100644 index 0000000000..45dd83f666 --- /dev/null +++ b/.changes/next-release/bugfix-Credentials-6b971f33.json @@ -0,0 +1,5 @@ +{ + "type": "bugfix", + "category": "Credentials", + "description": "fix credential_process credential resolution failure if it is the only item in source_profile when used by role_arn" +} \ No newline at end of file