Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: source_profile credential_process resolution when used by role_arn #4488

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/next-release/bugfix-Credentials-6b971f33.json
Original file line number Diff line number Diff line change
@@ -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"
}
56 changes: 43 additions & 13 deletions lib/credentials/shared_ini_file_credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,48 @@ 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 credentialsSetChain = [
[AWS.SharedIniFileCredentials, function() { return false; }],
[AWS.ProcessCredentials, function() {
return !profiles[sourceProfileName] ||
!profiles[sourceProfileName]['credential_process'];
}],
];
var i = credentialsSetChain.length;
var errs = [];
for (var index = 0; index < credentialsSetChain.length; index++) {
var credentials = credentialsSetChain[index][0], skip = credentialsSetChain[index][1];
if (skip()) {
i--;
continue;
}
this.loadRoleProfile(profiles, profile, credentials, function(err, data) {
i--;
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;
}
});
}
(function() {
var j = setInterval(function() {
if (i <= 0) {
errs.forEach(function(err) { callback(err); });
clearInterval(j);
}
}, 0);
})();
return;
}

Expand Down Expand Up @@ -176,7 +206,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. ' +
Expand Down Expand Up @@ -232,7 +262,7 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
);
}

var sourceCredentials = new AWS.SharedIniFileCredentials(
var sourceCredentials = new credentials(
AWS.util.merge(this.options || {}, {
profile: sourceProfileName,
preferStaticCredentials: true
Expand Down
59 changes: 59 additions & 0 deletions test/credentials.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1324,6 +1324,65 @@ 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) {
if (this.config.credentials.accessKeyId === 'akid' &&
this.config.credentials.secretAccessKey === 'secret' &&
this.config.credentials.sessionToken === 'session') {
callback(null, {
Credentials: {
AccessKeyId: 'KEY',
SecretAccessKey: 'SECRET',
SessionToken: 'TOKEN',
Expiration: expiration
}
});
}
callback(new Error('INVALID CREDENTIAL'));
}
);

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();
}
});
});

describe('mfa serial callback', function() {

beforeEach(function() {
Expand Down