Skip to content

Commit

Permalink
Load configuration and credentials from ~/.aws/config or path specifi…
Browse files Browse the repository at this point in the history
…ed in AWS_CONFIG_FILE if AWS_SDK_LOAD_CONFIG is set
  • Loading branch information
jeskew committed Apr 13, 2017
1 parent 2daf856 commit 44b1594
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "feature",
"category": "EnvironmentVariable",
"description": "Load config from ~/.aws/config if AWS_SDK_LOAD_CONFIG is set"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "feature",
"category": "EnvironmentVariable",
"description": "Add support for specifying the location of the shared config file via the AWS_CONFIG_FILE environment variable. This variable is only honored if AWS_SDK_LOAD_CONFIG is set to a truthy value."
}
69 changes: 21 additions & 48 deletions lib/credentials/shared_ini_file_credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ var path = require('path');
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
* `AWS_CREDENTIAL_PROFILES_FILE` environment variable).
* `AWS_SHARED_CREDENTIALS_FILE` environment variable).
*
* ## Using the shared credentials file
*
Expand Down Expand Up @@ -44,7 +45,7 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
* @option options profile [String] (AWS_PROFILE env var or 'default')
* the name of the profile to load.
* @option options filename [String] ('~/.aws/credentials' or defined by
* AWS_CREDENTIAL_PROFILES_FILE process env var)
* AWS_SHARED_CREDENTIALS_FILE process env var)
* the filename to use when loading credentials.
* @option options disableAssumeRole [Boolean] (false) True to disable
* support for profiles that assume an IAM role. If true, and an assume
Expand Down Expand Up @@ -75,21 +76,23 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
refresh: function refresh(callback) {
if (!callback) callback = function(err) { if (err) throw err; };
try {
if (!this.filename) this.loadDefaultFilename();
var profile = {};
if (process.env[configOptInEnv]) {
var configProfileName = this.profile === defaultProfile ?
'default' : 'profile ' + this.profile;
var config = AWS.util.ini
.parse(AWS.util.readFileSync(this.configFilename));
profile = AWS.util.merge(profile, config[configProfileName]);
var config = new AWS.SharedIniFile({
isConfig: true,
filename: process.env.AWS_CONFIG_FILE
});
profile = AWS.util.merge(profile, config.getProfile(this.profile));
}
var creds = AWS.util.ini.parse(AWS.util.readFileSync(this.filename));
profile = AWS.util.merge(profile, creds[this.profile]);
var creds = new AWS.SharedIniFile({
filename: this.filename ||
(process.env[configOptInEnv] && process.env[sharedFileEnv])
});
profile = AWS.util.merge(profile, creds.getProfile(this.profile));

if (Object.keys(profile).length === 0) {
throw AWS.util.error(
new Error('Profile ' + this.profile + ' not found in ' + this.filename),
new Error('Profile ' + this.profile + ' not found'),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}
Expand All @@ -105,8 +108,7 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {

if (!this.accessKeyId || !this.secretAccessKey) {
throw AWS.util.error(
new Error('Credentials not set in ' + this.filename +
' using profile ' + this.profile),
new Error('Credentials not set for profile ' + this.profile),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}
Expand All @@ -124,8 +126,7 @@ 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 + ' from ' +
this.filename),
'Failed to load profile ' + this.profile),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}
Expand All @@ -138,19 +139,17 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {

if (!sourceProfileName) {
throw AWS.util.error(
new Error('source_profile is not set in ' + this.filename +
' using profile ' + this.profile),
new Error('source_profile is not set using profile ' + this.profile),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}

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

if (typeof sourceProfile !== 'object') {
throw AWS.util.error(
new Error('source_profile ' + sourceProfileName + ' set in ' +
this.filename + ' using profile ' + this.profile +
' does not exist'),
new Error('source_profile ' + sourceProfileName + ' using profile '
+ this.profile + ' does not exist'),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}
Expand All @@ -166,8 +165,7 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
if (!sourceCredentials.accessKeyId || !sourceCredentials.secretAccessKey) {
throw AWS.util.error(
new Error('Credentials not set in source_profile ' +
sourceProfileName + ' set in ' + this.filename +
' using profile ' + this.profile),
sourceProfileName + ' using profile ' + this.profile),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}
Expand Down Expand Up @@ -197,30 +195,5 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
self.expireTime = data.Credentials.Expiration;
callback();
});
},

/**
* @api private
*/
loadDefaultFilename: function loadDefaultFilename() {
var env = process.env;

var home = env.HOME ||
env.USERPROFILE ||
(env.HOMEPATH ? ((env.HOMEDRIVE || 'C:/') + env.HOMEPATH) : null);
if (!home) {
throw AWS.util.error(
new Error('Cannot load credentials, HOME path not set'),
{ code: 'SharedIniFileCredentialsProviderFailure' }
);
}

this.filename = path.join(home, '.aws', 'credentials');

if (env[configOptInEnv]) {
this.filename = env.AWS_SHARED_CREDENTIALS_FILE || this.filename;
this.configFilename = env.AWS_CONFIG_FILE ||
path.join(home, '.aws', 'config');
}
}
});
17 changes: 16 additions & 1 deletion lib/node_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ 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');
Expand Down Expand Up @@ -59,7 +62,19 @@ AWS.util.update(AWS.Config.prototype.keys, {
return new AWS.CredentialProviderChain();
},
region: function() {
return process.env.AWS_REGION || process.env.AMAZON_REGION;
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;
}
return region;
}
});

Expand Down
59 changes: 59 additions & 0 deletions lib/shared_ini.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
var AWS = require('./core');
var os = require('os');
var path = require('path');

/**
* @api private
*/
AWS.SharedIniFile = AWS.util.inherit({
constructor: function SharedIniFile(options) {
options = options || {};

this.filename = options.filename;
this.isConfig = options.isConfig === true;
},

ensureFileLoaded: function loadFile() {
if (!this.parsedContents) {
this.parsedContents = AWS.util.ini.parse(
AWS.util.readFileSync(this.filename || this.getDefaultFilepath())
);
}
},

getDefaultFilepath: function getDefaultFilepath() {
return path.join(
this.getHomeDir(),
'.aws',
this.isConfig ? 'config' : 'credentials'
);
},

getHomeDir: function getHomeDir() {
if (typeof os.homedir === 'function') {
return os.homedir();
}

var env = process.env;
var home = env.HOME ||
env.USERPROFILE ||
(env.HOMEPATH ? ((env.HOMEDRIVE || 'C:/') + env.HOMEPATH) : null);

if (home) {
return home;
}

throw AWS.util.error(
new Error('Cannot load credentials, HOME path not set')
);
},

getProfile: function loadProfile(profile) {
this.ensureFileLoaded();

var profileIndex = profile !== AWS.util.defaultProfile && this.isConfig ?
'profile ' + profile : profile;

return this.parsedContents[profileIndex];
}
});
6 changes: 5 additions & 1 deletion lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -892,8 +892,12 @@ var util = {
if (rules.payload && resp.data[rules.payload]) {
resp.data[rules.payload] = resp.data[rules.payload].toString();
}
}
},

/**
* @api private
*/
defaultProfile: 'default'
};

module.exports = util;
58 changes: 58 additions & 0 deletions test/config.spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,64 @@ describe 'AWS.Config', ->
config = new AWS.Config()
expect(config.region).to.equal('us-west-2')

describe 'shared config file', ->
beforeEach ->
os = require('os')
helpers.spyOn(os, 'homedir').andReturn('/home/user')

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)

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)

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)

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'
process.env.AWS_SDK_LOAD_CONFIG = '1'
mock = '''
[default]
region = us-west-2
'''
helpers.spyOn(AWS.util, 'readFileSync').andReturn(mock)

config = new AWS.Config()
expect(config.region).to.equal('us-east-1')

it 'can be set to a string', ->
expect(configure(region: 'us-west-1').region).to.equal('us-west-1')

Expand Down
Loading

0 comments on commit 44b1594

Please sign in to comment.