Skip to content

Commit fc4d210

Browse files
committed
Allow profiles defined in ~/.aws/credentials to assume roles with source profiles defined in ~/.aws/config
1 parent 44b1594 commit fc4d210

File tree

6 files changed

+271
-68
lines changed

6 files changed

+271
-68
lines changed

lib/credentials/shared_ini_file_credentials.js

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
var AWS = require('../core');
22
var path = require('path');
3+
var SharedIniFile = require('../shared_ini');
34
var STS = require('../../clients/sts');
45

5-
var configOptInEnv = 'AWS_SDK_LOAD_CONFIG';
6-
var sharedFileEnv = 'AWS_SHARED_CREDENTIALS_FILE';
7-
var defaultProfile = 'default';
8-
96
/**
107
* Represents credentials loaded from shared credentials file
118
* (defaulting to ~/.aws/credentials or defined by the
@@ -57,7 +54,7 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
5754
options = options || {};
5855

5956
this.filename = options.filename;
60-
this.profile = options.profile || process.env.AWS_PROFILE || defaultProfile;
57+
this.profile = options.profile || process.env.AWS_PROFILE || AWS.util.defaultProfile;
6158
this.disableAssumeRole = Boolean(options.disableAssumeRole);
6259
this.get(function() {});
6360
},
@@ -76,19 +73,27 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
7673
refresh: function refresh(callback) {
7774
if (!callback) callback = function(err) { if (err) throw err; };
7875
try {
79-
var profile = {};
80-
if (process.env[configOptInEnv]) {
81-
var config = new AWS.SharedIniFile({
76+
var profiles = {};
77+
if (process.env[AWS.util.configOptInEnv]) {
78+
var config = new SharedIniFile({
8279
isConfig: true,
83-
filename: process.env.AWS_CONFIG_FILE
80+
filename: process.env[AWS.util.sharedConfigFileEnv]
8481
});
85-
profile = AWS.util.merge(profile, config.getProfile(this.profile));
82+
for (var i = 0, availableProfiles = config.getProfiles(); i < availableProfiles.length; i++) {
83+
profiles[availableProfiles[i]] = config.getProfile(availableProfiles[i]);
84+
}
8685
}
87-
var creds = new AWS.SharedIniFile({
86+
var creds = new SharedIniFile({
8887
filename: this.filename ||
89-
(process.env[configOptInEnv] && process.env[sharedFileEnv])
88+
(process.env[AWS.util.configOptInEnv] && process.env[AWS.util.sharedCredentialsFileEnv])
9089
});
91-
profile = AWS.util.merge(profile, creds.getProfile(this.profile));
90+
for (var i = 0, availableProfiles = creds.getProfiles(); i < availableProfiles.length; i++) {
91+
profiles[availableProfiles[i]] = AWS.util.merge(
92+
profiles[availableProfiles[i]] || {},
93+
creds.getProfile(availableProfiles[i])
94+
);
95+
}
96+
var profile = profiles[this.profile] || {};
9297

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

100105
if (profile['role_arn']) {
101-
this.loadRoleProfile(creds, profile, callback);
106+
this.loadRoleProfile(profiles, profile, callback);
102107
return;
103108
}
104109

@@ -126,7 +131,8 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
126131
if (this.disableAssumeRole) {
127132
throw AWS.util.error(
128133
new Error('Role assumption profiles are disabled. ' +
129-
'Failed to load profile ' + this.profile),
134+
'Failed to load profile ' + this.profile +
135+
' from ' + creds.filename),
130136
{ code: 'SharedIniFileCredentialsProviderFailure' }
131137
);
132138
}
@@ -144,7 +150,7 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
144150
);
145151
}
146152

147-
var sourceProfile = creds.getProfile(sourceProfileName);
153+
var sourceProfile = creds[sourceProfileName];
148154

149155
if (typeof sourceProfile !== 'object') {
150156
throw AWS.util.error(

lib/node_loader.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,15 @@ AWS.XML.Parser = require('./xml/node_parser');
1919
// Load Node HTTP client
2020
require('./http/node');
2121

22-
// Load Node shared ini file loader
23-
require('./shared_ini');
24-
2522
// Load custom credential providers
2623
require('./credentials/ec2_metadata_credentials');
2724
require('./credentials/ecs_credentials');
2825
require('./credentials/environment_credentials');
2926
require('./credentials/file_system_credentials');
3027
require('./credentials/shared_ini_file_credentials');
3128

29+
var SharedIniFile = require('./shared_ini');
30+
3231
// Setup default chain providers
3332
// If this changes, please update documentation for
3433
// AWS.CredentialProviderChain.defaultProviders in
@@ -64,15 +63,18 @@ AWS.util.update(AWS.Config.prototype.keys, {
6463
region: function() {
6564
var env = process.env;
6665
var region = env.AWS_REGION || env.AMAZON_REGION;
67-
if (!region && env.AWS_SDK_LOAD_CONFIG) {
68-
var configFile = new AWS.SharedIniFile({
69-
isConfig: true,
70-
filename: process.env.AWS_CONFIG_FILE
71-
});
72-
var profile = configFile.getProfile(
73-
env.AWS_PROFILE || AWS.util.defaultProfile
74-
);
75-
region = profile && profile.region;
66+
if (env[AWS.util.configOptInEnv]) {
67+
var toCheck = [
68+
{filename: env[AWS.util.sharedCredentialsFileEnv]},
69+
{isConfig: true, filename: env[AWS.util.sharedConfigFileEnv]}
70+
];
71+
while (!region && toCheck.length) {
72+
var configFile = new SharedIniFile(toCheck.shift());
73+
var profile = configFile.getProfile(
74+
env.AWS_PROFILE || AWS.util.defaultProfile
75+
);
76+
region = profile && profile.region;
77+
}
7678
}
7779
return region;
7880
}

lib/shared_ini.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@ var path = require('path');
55
/**
66
* @api private
77
*/
8-
AWS.SharedIniFile = AWS.util.inherit({
8+
module.exports = AWS.util.inherit({
99
constructor: function SharedIniFile(options) {
1010
options = options || {};
1111

12-
this.filename = options.filename;
1312
this.isConfig = options.isConfig === true;
13+
this.filename = options.filename || this.getDefaultFilepath();
1414
},
1515

1616
ensureFileLoaded: function loadFile() {
1717
if (!this.parsedContents) {
1818
this.parsedContents = AWS.util.ini.parse(
19-
AWS.util.readFileSync(this.filename || this.getDefaultFilepath())
19+
AWS.util.readFileSync(this.filename)
2020
);
2121
}
2222
},
@@ -55,5 +55,18 @@ AWS.SharedIniFile = AWS.util.inherit({
5555
'profile ' + profile : profile;
5656

5757
return this.parsedContents[profileIndex];
58+
},
59+
60+
getProfiles: function loadProfileNames() {
61+
this.ensureFileLoaded();
62+
var isConfig = this.isConfig;
63+
64+
return Object.keys(this.parsedContents).map(function(profileName) {
65+
if (isConfig) {
66+
return profileName.replace(/^profile\s/, '');
67+
}
68+
69+
return profileName;
70+
});
5871
}
5972
});

lib/util.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -897,7 +897,22 @@ var util = {
897897
/**
898898
* @api private
899899
*/
900-
defaultProfile: 'default'
900+
defaultProfile: 'default',
901+
902+
/**
903+
* @api private
904+
*/
905+
configOptInEnv: 'AWS_SDK_LOAD_CONFIG',
906+
907+
/**
908+
* @api private
909+
*/
910+
sharedCredentialsFileEnv: 'AWS_SHARED_CREDENTIALS_FILE',
911+
912+
/**
913+
* @api private
914+
*/
915+
sharedConfigFileEnv: 'AWS_CONFIG_FILE'
901916
};
902917

903918
module.exports = util;

test/config.spec.coffee

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
helpers = require('./helpers')
22
AWS = helpers.AWS
3+
SharedIniFile = require('../lib/shared_ini')
34

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

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

59+
it 'grabs region from shared credentials file if AWS_SDK_LOAD_CONFIG is set', ->
60+
process.env.AWS_SDK_LOAD_CONFIG = '1'
61+
helpers.spyOn(AWS.util, 'readFileSync').andCallFake (path) ->
62+
if (path.match(/[\/\\]home[\/\\]user[\/\\].aws[\/\\]credentials/))
63+
'''
64+
[default]
65+
region = us-west-2
66+
'''
67+
else
68+
'''
69+
[default]
70+
region = eu-east-1
71+
'''
72+
73+
config = new AWS.Config()
74+
expect(config.region).to.equal('us-west-2')
75+
76+
it 'loads file from path specified in AWS_SHARED_CREDENTIALS_FILE if AWS_SDK_LOAD_CONFIG is set', ->
77+
process.env.AWS_SDK_LOAD_CONFIG = '1'
78+
process.env.AWS_SHARED_CREDENTIALS_FILE = '/path/to/user/config/file'
79+
helpers.spyOn(AWS.util, 'readFileSync').andCallFake (path) ->
80+
if (path == '/path/to/user/config/file')
81+
'''
82+
[default]
83+
region = us-west-2
84+
'''
85+
else
86+
'''
87+
[default]
88+
region = eu-east-1
89+
'''
90+
91+
config = new AWS.Config()
92+
expect(config.region).to.equal('us-west-2')
93+
5894
it 'grabs region from shared config if AWS_SDK_LOAD_CONFIG is set', ->
5995
process.env.AWS_SDK_LOAD_CONFIG = '1'
60-
mock = '''
61-
[default]
62-
region = us-west-2
63-
'''
64-
helpers.spyOn(AWS.util, 'readFileSync').andReturn(mock)
96+
helpers.spyOn(AWS.util, 'readFileSync').andCallFake (path) ->
97+
if (path.match(/[\/\\]home[\/\\]user[\/\\].aws[\/\\]config/))
98+
'''
99+
[default]
100+
region = us-west-2
101+
'''
102+
else
103+
'''
104+
[default]
105+
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
106+
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
107+
'''
65108

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

70112
it 'loads file from path specified in AWS_CONFIG_FILE if AWS_SDK_LOAD_CONFIG is set', ->
71113
process.env.AWS_SDK_LOAD_CONFIG = '1'
72114
process.env.AWS_CONFIG_FILE = '/path/to/user/config/file'
73-
mock = '''
74-
[default]
75-
region = us-west-2
76-
'''
77-
helpers.spyOn(AWS.util, 'readFileSync').andReturn(mock)
115+
helpers.spyOn(AWS.util, 'readFileSync').andCallFake (path) ->
116+
if (path == '/path/to/user/config/file')
117+
'''
118+
[default]
119+
region = us-west-2
120+
'''
121+
else
122+
'''
123+
[default]
124+
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
125+
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
126+
'''
78127

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

83131
it 'uses the profile specified in AWS_PROFILE', ->
84132
process.env.AWS_SDK_LOAD_CONFIG = '1'
85133
process.env.AWS_PROFILE = 'foo'
86-
mock = '''
87-
[default]
88-
region = us-west-1
89-
90-
[profile foo]
91-
region = us-west-2
92-
'''
93-
helpers.spyOn(AWS.util, 'readFileSync').andReturn(mock)
134+
helpers.spyOn(AWS.util, 'readFileSync').andCallFake (path) ->
135+
if (path.match(/[\/\\]home[\/\\]user[\/\\].aws[\/\\]config/))
136+
'''
137+
[default]
138+
region = us-west-1
139+
140+
[profile foo]
141+
region = us-west-2
142+
'''
143+
else
144+
'''
145+
[default]
146+
region = eu-east-1
147+
'''
94148

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

99152
it 'prefers AWS_REGION to the shared config file', ->
100153
process.env.AWS_REGION = 'us-east-1'

0 commit comments

Comments
 (0)