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

implement credential_process on ProcessCredentials provider #2559

Merged
merged 13 commits into from
Mar 26, 2019
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
srchase marked this conversation as resolved.
Show resolved Hide resolved
}
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]];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this part same to that in SharedIniFileCredential? It's a relatively minor one but I'd like to put this functionality(loading correct profile) in the util function and attach to AWS.util in node_loader.

Copy link
Contributor Author

@srchase srchase Mar 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's almost the same, but it loads profilesFromConfig after profilesFromCreds. If credentials and config files had profiles with the same name, SharedIniFileCredentials and ProcessCredentials would give different precedence.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Thanks for clearing.

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(
srchase marked this conversation as resolved.
Show resolved Hide resolved
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()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the diff for EcsCredentials. Do I miss it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ECSCredentials already throws an error if ENV_RELATIVE_URI or ENV_FULL_URI aren't set. isConfiguredForEcsCredentials could be removed if ECSCredentials is going to be put into the default providers.

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