Skip to content

Commit

Permalink
Allow profiles defined in ~/.aws/credentials to assume roles with sou…
Browse files Browse the repository at this point in the history
…rce profiles defined in ~/.aws/config
  • Loading branch information
jeskew committed Apr 13, 2017
1 parent 44b1594 commit fc4d210
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 68 deletions.
38 changes: 22 additions & 16 deletions lib/credentials/shared_ini_file_credentials.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
var AWS = require('../core');
var path = require('path');
var SharedIniFile = require('../shared_ini');
var STS = require('../../clients/sts');

var configOptInEnv = 'AWS_SDK_LOAD_CONFIG';
var sharedFileEnv = 'AWS_SHARED_CREDENTIALS_FILE';
var defaultProfile = 'default';

/**
* Represents credentials loaded from shared credentials file
* (defaulting to ~/.aws/credentials or defined by the
Expand Down Expand Up @@ -57,7 +54,7 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
options = options || {};

this.filename = options.filename;
this.profile = options.profile || process.env.AWS_PROFILE || defaultProfile;
this.profile = options.profile || process.env.AWS_PROFILE || AWS.util.defaultProfile;
this.disableAssumeRole = Boolean(options.disableAssumeRole);
this.get(function() {});
},
Expand All @@ -76,19 +73,27 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
refresh: function refresh(callback) {
if (!callback) callback = function(err) { if (err) throw err; };
try {
var profile = {};
if (process.env[configOptInEnv]) {
var config = new AWS.SharedIniFile({
var profiles = {};
if (process.env[AWS.util.configOptInEnv]) {
var config = new SharedIniFile({
isConfig: true,
filename: process.env.AWS_CONFIG_FILE
filename: process.env[AWS.util.sharedConfigFileEnv]
});
profile = AWS.util.merge(profile, config.getProfile(this.profile));
for (var i = 0, availableProfiles = config.getProfiles(); i < availableProfiles.length; i++) {
profiles[availableProfiles[i]] = config.getProfile(availableProfiles[i]);
}
}
var creds = new AWS.SharedIniFile({
var creds = new SharedIniFile({
filename: this.filename ||
(process.env[configOptInEnv] && process.env[sharedFileEnv])
(process.env[AWS.util.configOptInEnv] && process.env[AWS.util.sharedCredentialsFileEnv])
});
profile = AWS.util.merge(profile, creds.getProfile(this.profile));
for (var i = 0, availableProfiles = creds.getProfiles(); i < availableProfiles.length; i++) {
profiles[availableProfiles[i]] = AWS.util.merge(
profiles[availableProfiles[i]] || {},
creds.getProfile(availableProfiles[i])
);
}
var profile = profiles[this.profile] || {};

if (Object.keys(profile).length === 0) {
throw AWS.util.error(
Expand All @@ -98,7 +103,7 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
}

if (profile['role_arn']) {
this.loadRoleProfile(creds, profile, callback);
this.loadRoleProfile(profiles, profile, callback);
return;
}

Expand Down Expand Up @@ -126,7 +131,8 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
if (this.disableAssumeRole) {
throw AWS.util.error(
new Error('Role assumption profiles are disabled. ' +
'Failed to load profile ' + this.profile),
'Failed to load profile ' + this.profile +
' from ' + creds.filename),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}
Expand All @@ -144,7 +150,7 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
);
}

var sourceProfile = creds.getProfile(sourceProfileName);
var sourceProfile = creds[sourceProfileName];

if (typeof sourceProfile !== 'object') {
throw AWS.util.error(
Expand Down
26 changes: 14 additions & 12 deletions lib/node_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,15 @@ AWS.XML.Parser = require('./xml/node_parser');
// Load Node HTTP client
require('./http/node');

// Load Node shared ini file loader
require('./shared_ini');

// Load custom credential providers
require('./credentials/ec2_metadata_credentials');
require('./credentials/ecs_credentials');
require('./credentials/environment_credentials');
require('./credentials/file_system_credentials');
require('./credentials/shared_ini_file_credentials');

var SharedIniFile = require('./shared_ini');

// Setup default chain providers
// If this changes, please update documentation for
// AWS.CredentialProviderChain.defaultProviders in
Expand Down Expand Up @@ -64,15 +63,18 @@ AWS.util.update(AWS.Config.prototype.keys, {
region: function() {
var env = process.env;
var region = env.AWS_REGION || env.AMAZON_REGION;
if (!region && env.AWS_SDK_LOAD_CONFIG) {
var configFile = new AWS.SharedIniFile({
isConfig: true,
filename: process.env.AWS_CONFIG_FILE
});
var profile = configFile.getProfile(
env.AWS_PROFILE || AWS.util.defaultProfile
);
region = profile && profile.region;
if (env[AWS.util.configOptInEnv]) {
var toCheck = [
{filename: env[AWS.util.sharedCredentialsFileEnv]},
{isConfig: true, filename: env[AWS.util.sharedConfigFileEnv]}
];
while (!region && toCheck.length) {
var configFile = new SharedIniFile(toCheck.shift());
var profile = configFile.getProfile(
env.AWS_PROFILE || AWS.util.defaultProfile
);
region = profile && profile.region;
}
}
return region;
}
Expand Down
19 changes: 16 additions & 3 deletions lib/shared_ini.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ var path = require('path');
/**
* @api private
*/
AWS.SharedIniFile = AWS.util.inherit({
module.exports = AWS.util.inherit({
constructor: function SharedIniFile(options) {
options = options || {};

this.filename = options.filename;
this.isConfig = options.isConfig === true;
this.filename = options.filename || this.getDefaultFilepath();
},

ensureFileLoaded: function loadFile() {
if (!this.parsedContents) {
this.parsedContents = AWS.util.ini.parse(
AWS.util.readFileSync(this.filename || this.getDefaultFilepath())
AWS.util.readFileSync(this.filename)
);
}
},
Expand Down Expand Up @@ -55,5 +55,18 @@ AWS.SharedIniFile = AWS.util.inherit({
'profile ' + profile : profile;

return this.parsedContents[profileIndex];
},

getProfiles: function loadProfileNames() {
this.ensureFileLoaded();
var isConfig = this.isConfig;

return Object.keys(this.parsedContents).map(function(profileName) {
if (isConfig) {
return profileName.replace(/^profile\s/, '');
}

return profileName;
});
}
});
17 changes: 16 additions & 1 deletion lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,22 @@ var util = {
/**
* @api private
*/
defaultProfile: 'default'
defaultProfile: 'default',

/**
* @api private
*/
configOptInEnv: 'AWS_SDK_LOAD_CONFIG',

/**
* @api private
*/
sharedCredentialsFileEnv: 'AWS_SHARED_CREDENTIALS_FILE',

/**
* @api private
*/
sharedConfigFileEnv: 'AWS_CONFIG_FILE'
};

module.exports = util;
95 changes: 74 additions & 21 deletions test/config.spec.coffee
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
helpers = require('./helpers')
AWS = helpers.AWS
SharedIniFile = require('../lib/shared_ini')

configure = (options) -> new AWS.Config(options)

Expand Down Expand Up @@ -55,46 +56,98 @@ describe 'AWS.Config', ->
os = require('os')
helpers.spyOn(os, 'homedir').andReturn('/home/user')

it 'grabs region from shared credentials file if AWS_SDK_LOAD_CONFIG is set', ->
process.env.AWS_SDK_LOAD_CONFIG = '1'
helpers.spyOn(AWS.util, 'readFileSync').andCallFake (path) ->
if (path.match(/[\/\\]home[\/\\]user[\/\\].aws[\/\\]credentials/))
'''
[default]
region = us-west-2
'''
else
'''
[default]
region = eu-east-1
'''

config = new AWS.Config()
expect(config.region).to.equal('us-west-2')

it 'loads file from path specified in AWS_SHARED_CREDENTIALS_FILE if AWS_SDK_LOAD_CONFIG is set', ->
process.env.AWS_SDK_LOAD_CONFIG = '1'
process.env.AWS_SHARED_CREDENTIALS_FILE = '/path/to/user/config/file'
helpers.spyOn(AWS.util, 'readFileSync').andCallFake (path) ->
if (path == '/path/to/user/config/file')
'''
[default]
region = us-west-2
'''
else
'''
[default]
region = eu-east-1
'''

config = new AWS.Config()
expect(config.region).to.equal('us-west-2')

it 'grabs region from shared config if AWS_SDK_LOAD_CONFIG is set', ->
process.env.AWS_SDK_LOAD_CONFIG = '1'
mock = '''
[default]
region = us-west-2
'''
helpers.spyOn(AWS.util, 'readFileSync').andReturn(mock)
helpers.spyOn(AWS.util, 'readFileSync').andCallFake (path) ->
if (path.match(/[\/\\]home[\/\\]user[\/\\].aws[\/\\]config/))
'''
[default]
region = us-west-2
'''
else
'''
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
'''

config = new AWS.Config()
expect(config.region).to.equal('us-west-2')
expect(AWS.util.readFileSync.calls[0].arguments[0]).to.match(/[\/\\]home[\/\\]user[\/\\].aws[\/\\]config/)

it 'loads file from path specified in AWS_CONFIG_FILE if AWS_SDK_LOAD_CONFIG is set', ->
process.env.AWS_SDK_LOAD_CONFIG = '1'
process.env.AWS_CONFIG_FILE = '/path/to/user/config/file'
mock = '''
[default]
region = us-west-2
'''
helpers.spyOn(AWS.util, 'readFileSync').andReturn(mock)
helpers.spyOn(AWS.util, 'readFileSync').andCallFake (path) ->
if (path == '/path/to/user/config/file')
'''
[default]
region = us-west-2
'''
else
'''
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
'''

config = new AWS.Config()
expect(config.region).to.equal('us-west-2')
expect(AWS.util.readFileSync.calls[0].arguments[0]).to.equal('/path/to/user/config/file')

it 'uses the profile specified in AWS_PROFILE', ->
process.env.AWS_SDK_LOAD_CONFIG = '1'
process.env.AWS_PROFILE = 'foo'
mock = '''
[default]
region = us-west-1
[profile foo]
region = us-west-2
'''
helpers.spyOn(AWS.util, 'readFileSync').andReturn(mock)
helpers.spyOn(AWS.util, 'readFileSync').andCallFake (path) ->
if (path.match(/[\/\\]home[\/\\]user[\/\\].aws[\/\\]config/))
'''
[default]
region = us-west-1
[profile foo]
region = us-west-2
'''
else
'''
[default]
region = eu-east-1
'''

config = new AWS.Config()
expect(config.region).to.equal('us-west-2')
expect(AWS.util.readFileSync.calls[0].arguments[0]).to.match(/[\/\\]home[\/\\]user[\/\\].aws[\/\\]config/)

it 'prefers AWS_REGION to the shared config file', ->
process.env.AWS_REGION = 'us-east-1'
Expand Down
Loading

0 comments on commit fc4d210

Please sign in to comment.