Skip to content

Commit

Permalink
Merge pull request #2559 from srchase/credential-process
Browse files Browse the repository at this point in the history
implement credential_process on ProcessCredentials provider
  • Loading branch information
srchase authored Mar 26, 2019
2 parents 33018fb + cd5aaf2 commit 0a6ca3f
Show file tree
Hide file tree
Showing 8 changed files with 418 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changes/next-release/feature-Credentials-1a22eb00.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "feature",
"category": "Credentials",
"description": "enables use of credentials_process for sourcing credentials from an external process https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes"
}
1 change: 1 addition & 0 deletions lib/core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export {EnvironmentCredentials} from './credentials/environment_credentials';
export {FileSystemCredentials} from './credentials/file_system_credentials';
export {SAMLCredentials} from './credentials/saml_credentials';
export {SharedIniFileCredentials} from './credentials/shared_ini_file_credentials';
export {ProcessCredentials} from './credentials/process_credentials';
export {TemporaryCredentials} from './credentials/temporary_credentials';
export {ChainableTemporaryCredentials} from './credentials/chainable_temporary_credentials';
export {WebIdentityCredentials} from './credentials/web_identity_credentials';
Expand Down
9 changes: 3 additions & 6 deletions lib/credentials/credential_provider_chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,9 @@ AWS.CredentialProviderChain = AWS.util.inherit(AWS.Credentials, {
* function () { return new AWS.EnvironmentCredentials('AWS'); },
* function () { return new AWS.EnvironmentCredentials('AMAZON'); },
* function () { return new AWS.SharedIniFileCredentials(); },
* function () {
* // if AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is set
* return new AWS.ECSCredentials();
* // else
* return new AWS.EC2MetadataCredentials();
* }
* function () { return new AWS.ECSCredentials(); },
* function () { return new AWS.ProcessCredentials(); },
* function () { return new AWS.EC2MetadataCredentials() }
* ]
* ```
*/
Expand Down
14 changes: 14 additions & 0 deletions lib/credentials/process_credentials.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {Credentials} from '../credentials';
import {HTTPOptions} from '../config';
export class ProcessCredentials extends Credentials {
/**
* Creates a new ProcessCredentials object.
*/
constructor(options?: ProcessCredentialsOptions);
}

interface ProcessCredentialsOptions {
profile?: string
filename?: string
httpOptions?: HTTPOptions
}
182 changes: 182 additions & 0 deletions lib/credentials/process_credentials.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
var AWS = require('../core');
var proc = require('child_process');
var iniLoader = AWS.util.iniLoader;

/**
* Represents credentials loaded from shared credentials file
* (defaulting to ~/.aws/credentials or defined by the
* `AWS_SHARED_CREDENTIALS_FILE` environment variable).
*
* ## Using process credentials
*
* The credentials file can specify a credential provider that executes
* a given process and attempts to read its stdout to recieve a JSON payload
* containing the credentials:
*
* [default]
* credential_process = /usr/bin/credential_proc
*
* Automatically handles refreshing credentials if an Expiration time is
* provided in the credentials payload. Credentials supplied in the same profile
* will take precedence over the credential_process.
*
* Sourcing credentials from an external process can potentially be dangerous,
* so proceed with caution. Other credential providers should be preferred if
* at all possible. If using this option, you should make sure that the shared
* credentials file is as locked down as possible using security best practices
* for your operating system.
*
* ## Using custom profiles
*
* The SDK supports loading credentials for separate profiles. This can be done
* in two ways:
*
* 1. Set the `AWS_PROFILE` environment variable in your process prior to
* loading the SDK.
* 2. Directly load the AWS.ProcessCredentials provider:
*
* ```javascript
* var creds = new AWS.ProcessCredentials({profile: 'myprofile'});
* AWS.config.credentials = creds;
* ```
*
* @!macro nobrowser
*/
AWS.ProcessCredentials = AWS.util.inherit(AWS.Credentials, {
/**
* Creates a new ProcessCredentials object.
*
* @param options [map] a set of options
* @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_SHARED_CREDENTIALS_FILE process env var)
* the filename to use when loading credentials.
* @option options callback [Function] (err) Credentials are eagerly loaded
* by the constructor. When the callback is called with no error, the
* credentials have been loaded successfully.
*/
constructor: function ProcessCredentials(options) {
AWS.Credentials.call(this);

options = options || {};

this.filename = options.filename;
this.profile = options.profile || process.env.AWS_PROFILE || AWS.util.defaultProfile;
this.get(options.callback || AWS.util.fn.noop);
},

/**
* @api private
*/
load: function load(callback) {
var self = this;
try {
var profiles = {};
var profilesFromConfig = {};
if (process.env[AWS.util.configOptInEnv]) {
var profilesFromConfig = iniLoader.loadFrom({
isConfig: true,
filename: process.env[AWS.util.sharedConfigFileEnv]
});
}
var profilesFromCreds = iniLoader.loadFrom({
filename: this.filename ||
(process.env[AWS.util.configOptInEnv] && process.env[AWS.util.sharedCredentialsFileEnv])
});
for (var i = 0, profileNames = Object.keys(profilesFromCreds); i < profileNames.length; i++) {
profiles[profileNames[i]] = profilesFromCreds[profileNames[i]];
}
// load after profilesFromCreds to prefer profilesFromConfig
for (var i = 0, profileNames = Object.keys(profilesFromConfig); i < profileNames.length; i++) {
profiles[profileNames[i]] = profilesFromConfig[profileNames[i]];
}
var profile = profiles[this.profile] || {};

if (Object.keys(profile).length === 0) {
throw AWS.util.error(
new Error('Profile ' + this.profile + ' not found'),
{ code: 'ProcessCredentialsProviderFailure' }
);
}

if (profile['credential_process']) {
this.loadViaCredentialProcess(profile, function(err, data) {
if (err) {
callback(err, null);
} else {
self.expired = false;
self.accessKeyId = data.AccessKeyId;
self.secretAccessKey = data.SecretAccessKey;
self.sessionToken = data.SessionToken;
if (data.Expiration) {
self.expireTime = new Date(data.Expiration);
}
callback(null);
}
});
} else {
throw AWS.util.error(
new Error('Profile ' + this.profile + ' did not include credential process'),
{ code: 'ProcessCredentialsProviderFailure' }
);
}
} catch (err) {
callback(err);
}
},

/**
* Executes the credential_process and retrieves
* credentials from the output
* @api private
* @param profile [map] credentials profile
* @throws ProcessCredentialsProviderFailure
*/
loadViaCredentialProcess: function loadViaCredentialProcess(profile, callback) {
proc.exec(profile['credential_process'], function(err, stdOut, stdErr) {
if (err) {
callback(AWS.util.error(
new Error('credential_process returned error'),
{ code: 'ProcessCredentialsProviderFailure'}
), null);
} else {
try {
var credData = JSON.parse(stdOut);
if (credData.Expiration) {
var currentTime = AWS.util.date.getDate();
var expireTime = new Date(credData.Expiration);
if (expireTime < currentTime) {
throw Error('credential_process returned expired credentials');
}
}

if (credData.Version !== 1) {
throw Error('credential_process does not return Version == 1');
}
callback(null, credData);
} catch (err) {
callback(AWS.util.error(
new Error(err.message),
{ code: 'ProcessCredentialsProviderFailure'}
), null);
}
}
});
},

/**
* Loads the credentials from the credential process
*
* @callback callback function(err)
* Called after the credential process has been executed. When this
* callback is called with no error, it means that the credentials
* information has been loaded into the object (as the `accessKeyId`,
* `secretAccessKey`, and `sessionToken` properties).
* @param err [Error] if an error occurred, this value will be filled
* @see get
*/
refresh: function refresh(callback) {
this.coalesceRefresh(callback || AWS.util.fn.callback);
}
});
6 changes: 0 additions & 6 deletions lib/credentials/shared_ini_file_credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,6 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
* * **timeout** [Integer] &mdash; Sets the socket to timeout after timeout
* milliseconds of inactivity on the socket. Defaults to two minutes
* (120000).
* * **xhrAsync** [Boolean] &mdash; Whether the SDK will send asynchronous
* HTTP requests. Used in the browser environment only. Set to false to
* send requests synchronously. Defaults to true (async on).
* * **xhrWithCredentials** [Boolean] &mdash; Sets the "withCredentials"
* property of an XMLHttpRequest object. Used in the browser environment
* only. Defaults to false.
*/
constructor: function SharedIniFileCredentials(options) {
AWS.Credentials.call(this);
Expand Down
11 changes: 5 additions & 6 deletions lib/node_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ require('./credentials/chainable_temporary_credentials');
require('./credentials/web_identity_credentials');
require('./credentials/cognito_identity_credentials');
require('./credentials/saml_credentials');
require('./credentials/process_credentials');

// Load the xml2js XML parser
AWS.XML.Parser = require('./xml/node_parser');
Expand All @@ -50,6 +51,7 @@ require('./credentials/ecs_credentials');
require('./credentials/environment_credentials');
require('./credentials/file_system_credentials');
require('./credentials/shared_ini_file_credentials');
require('./credentials/process_credentials');

// Setup default chain providers
// If this changes, please update documentation for
Expand All @@ -59,12 +61,9 @@ AWS.CredentialProviderChain.defaultProviders = [
function () { return new AWS.EnvironmentCredentials('AWS'); },
function () { return new AWS.EnvironmentCredentials('AMAZON'); },
function () { return new AWS.SharedIniFileCredentials(); },
function () {
if (AWS.ECSCredentials.prototype.isConfiguredForEcsCredentials()) {
return new AWS.ECSCredentials();
}
return new AWS.EC2MetadataCredentials();
}
function () { return new AWS.ECSCredentials(); },
function () { return new AWS.ProcessCredentials(); },
function () { return new AWS.EC2MetadataCredentials(); }
];

// Update configuration keys
Expand Down
Loading

0 comments on commit 0a6ca3f

Please sign in to comment.