-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix for #13 and adds test target and notes.
- Loading branch information
Showing
13 changed files
with
10,234 additions
and
1,759 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
'use strict'; | ||
const join = require('path').join | ||
|
||
module.exports = require(__dirname + '/lib/aws-sigv4'); | ||
module.exports = require(join(__dirname, '/lib/aws-sigv4')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,155 +1,181 @@ | ||
'use strict'; | ||
|
||
var aws = require('aws-sdk'), | ||
url = require('url'), | ||
constants = { | ||
PLUGIN_PREFIX: 'artillery-plugin-', | ||
PLUGIN_NAME: 'aws-sigv4', | ||
PLUGIN_PARAM_SERVICE_NAME: 'serviceName', | ||
THE: 'The "', | ||
CONFIG_REQUIRED: '" plugin requires configuration under [script].config.plugins.', | ||
PARAM_REQUIRED: '" parameter is required', | ||
PARAM_MUST_BE_STRING: '" param must have a string value', | ||
HEADER_AUTHORIZATION: 'Authorization', | ||
ERROR: ' ERROR (signature will not be added): ' | ||
}, | ||
messages = { | ||
pluginConfigRequired: constants.THE + constants.PLUGIN_NAME + constants.CONFIG_REQUIRED + constants.PLUGIN_NAME, | ||
pluginParamServiceNameRequired: constants.THE + constants.PLUGIN_PARAM_SERVICE_NAME + constants.PARAM_REQUIRED, | ||
pluginParamServiceNameMustBeString: constants.THE + constants.PLUGIN_PARAM_SERVICE_NAME + constants.PARAM_MUST_BE_STRING, | ||
sdkConfigInvalidError: constants.PLUGIN_PREFIX + constants.PLUGIN_NAME + constants.ERROR | ||
}, | ||
impl = { | ||
validateScriptConfig: function(scriptConfig) { | ||
// Validate that plugin config exists | ||
if (!(scriptConfig && scriptConfig.plugins && constants.PLUGIN_NAME in scriptConfig.plugins)) { | ||
throw new Error(messages.pluginConfigRequired); | ||
} | ||
// Validate NAMESPACE | ||
if (!(constants.PLUGIN_PARAM_SERVICE_NAME in scriptConfig.plugins[constants.PLUGIN_NAME])) { | ||
throw new Error(messages.pluginParamServiceNameRequired); | ||
} else if (!('string' === typeof scriptConfig.plugins[constants.PLUGIN_NAME][constants.PLUGIN_PARAM_SERVICE_NAME] || | ||
scriptConfig.plugins[constants.PLUGIN_NAME][constants.PLUGIN_PARAM_SERVICE_NAME] instanceof String)) { | ||
throw new Error(messages.pluginParamServiceNameMustBeString); | ||
} | ||
}, | ||
validateSdkConfig: function(credentials, region) { | ||
var result = false; | ||
if (!credentials) { | ||
console.log([ | ||
messages.sdkConfigInvalidError, | ||
'credentials not obtained. ', | ||
'Ensure the aws-sdk can obtain valid credentials.' | ||
].join('')); | ||
} else if ( | ||
!( | ||
(credentials.accessKeyId && credentials.secretAccessKey) || | ||
credentials.roleArn | ||
) | ||
) { | ||
console.log([ | ||
messages.sdkConfigInvalidError, | ||
'valid credentials not loaded. ', | ||
'Ensure the aws-sdk can obtain credentials with either both accessKeyId and ', | ||
'secretAccessKey attributes (optionally sessionToken) or a roleArn attribute.' | ||
].join('')); | ||
} else if (!region) { | ||
console.log([ | ||
messages.sdkConfigInvalidError, | ||
'valid region not configured. ', | ||
'Ensure the aws-sdk can obtain a valid region for use in signing your requests. ', | ||
'Consider exporting or setting AWS_REGION. Alternatively specify a default ', | ||
'region in your ~/.aws/config file.' | ||
].join('')); | ||
} else { | ||
result = true; | ||
} | ||
return result; | ||
}, | ||
addAmazonSignatureV4: function(serviceName, requestParams, context, ee, callback) { | ||
var targetUrl = url.parse(requestParams.uri || requestParams.url), | ||
credentials = aws.config.credentials, | ||
region = aws.config.region, | ||
end = new aws.Endpoint(targetUrl.hostname), | ||
req = new aws.HttpRequest(end), | ||
signer, | ||
header; | ||
|
||
if (impl.validateSdkConfig(credentials, region)) { | ||
req.method = requestParams.method; | ||
req.path = targetUrl.path; | ||
req.region = region; | ||
req.headers.Host = end.host; | ||
|
||
for (header in requestParams.headers) { | ||
req.headers[header] = requestParams.headers[header]; | ||
} | ||
|
||
if (requestParams.body) { | ||
req.body = requestParams.body; | ||
} else if (requestParams.json) { | ||
req.body = JSON.stringify(requestParams.json); | ||
} | ||
|
||
signer = new aws.Signers.V4(req, serviceName); | ||
signer.addAuthorization(credentials, new Date()); | ||
|
||
for (header in req.headers) { | ||
requestParams.headers[header] = req.headers[header]; | ||
} | ||
} | ||
callback(); | ||
} | ||
}, | ||
api = { | ||
init: function(scriptConfig, eventEmitter) { | ||
var AwsSigV4Plugin = function(scriptConfig, eventEmitter) { | ||
var serviceName, | ||
sdkCredentials = false, | ||
sdkCredentialsError, | ||
p; | ||
impl.validateScriptConfig(scriptConfig); | ||
serviceName = scriptConfig.plugins[constants.PLUGIN_NAME][constants.PLUGIN_PARAM_SERVICE_NAME]; | ||
aws.config.getCredentials(function(err) { | ||
if (err) { | ||
sdkCredentialsError = err; | ||
} else { | ||
sdkCredentials = true; | ||
if (p) { | ||
impl.addAmazonSignatureV4(serviceName, p.requestParams, p.context, p.ee, p.callback); | ||
} | ||
} | ||
}); | ||
if (!scriptConfig.processor) { | ||
scriptConfig.processor = {}; | ||
} | ||
scriptConfig.processor.addAmazonSignatureV4 = function(requestParams, context, ee, callback) { | ||
if (!sdkCredentials) { | ||
if (sdkCredentialsError) { | ||
console.log([ | ||
messages.sdkConfigInvalidError, | ||
'credentials fetch error. ', | ||
'Ensure the aws-sdk can obtain valid credentials. ', | ||
'Error: ', | ||
sdkCredentialsError.message | ||
].join('')); | ||
} else { | ||
p = { requestParams: requestParams, context: context, ee: ee, callback: callback }; | ||
} | ||
} else { | ||
impl.addAmazonSignatureV4(serviceName, requestParams, context, ee, callback); | ||
} | ||
}; | ||
}; | ||
return new AwsSigV4Plugin(scriptConfig, eventEmitter); | ||
'use strict' | ||
|
||
const aws = require('aws-sdk') | ||
const URL = require('url').URL | ||
|
||
const PLUGIN_NAME = 'aws-sigv4' | ||
|
||
const messages = { | ||
pluginConfigRequired: `The ${PLUGIN_NAME} plugin requires configuration under [script].config.plugins.${PLUGIN_NAME}.`, | ||
pluginParamServiceNameRequired: 'The "serviceName" parameter is required.', | ||
pluginParamServiceNameMustBeString: 'The "serviceName" parameter must have a string value.', | ||
sdkConfigInvalidError: `artillery-plugin-${PLUGIN_NAME} ERROR (signature will not be added): ` | ||
} | ||
|
||
const impl = { | ||
// Check to see if the script contains a valid configuration. | ||
validateScriptConfig: function (scriptConfig) { | ||
// Validate that plugin config exists | ||
if (!(scriptConfig && scriptConfig.plugins && scriptConfig.plugins[PLUGIN_NAME])) { | ||
throw new Error(messages.pluginConfigRequired) | ||
} | ||
|
||
// Validate serviceName in config | ||
const serviceNameConfig = scriptConfig.plugins[PLUGIN_NAME].serviceName | ||
if (!serviceNameConfig) { | ||
throw new Error(messages.pluginParamServiceNameRequired) | ||
} else if (!(typeof serviceNameConfig !== 'string' || (serviceNameConfig instanceof String))) { | ||
throw new Error(messages.pluginParamServiceNameMustBeString) | ||
} | ||
}, | ||
|
||
// Check to see if the AWS SDK configuration are valid. | ||
// Returns true if config is valid, false otherwise. | ||
validateSdkConfig: function (credentials, region) { | ||
// Some credentials must be found. | ||
if (!credentials) { | ||
console.log(`${messages.sdkConfigInvalidError} credentials not obtained.`) | ||
console.log('Ensure the aws-sdk can obtain valid credentials.') | ||
return false | ||
} | ||
|
||
// Check that the credentials contain either an access id and key pair or a Role ARN. | ||
const validIdAndKeyPair = (credentials.accessKeyId && credentials.secretAccessKey) | ||
if (!(validIdAndKeyPair || credentials.roleArn)) { | ||
console.log(`${messages.sdkConfigInvalidError} valid credentials not loaded.`) | ||
console.log('Ensure the aws-sdk can obtain credentials with either both accessKeyId and secretAccessKey attributes (optionally sessionToken) or a roleArn attribute.') | ||
return false | ||
} | ||
|
||
// A valid AWS region must be configured. | ||
if (!region) { | ||
console.log(`${messages.sdkConfigInvalidError} valid region not configured.`) | ||
console.log('Ensure the aws-sdk can obtain a valid region for use in signing your requests.') | ||
console.log('Consider exporting or setting AWS_REGION or alternatively specify a default region in your AWS config file.') | ||
return false | ||
} | ||
|
||
return true | ||
}, | ||
|
||
// Adds an Amazon V4 signature to the Artillery requests. | ||
addAmazonSignatureV4: function (awsOptions, requestParams, context, ee, callback) { | ||
const url = requestParams.uri || requestParams.url | ||
|
||
// Perform variable substitution in url. | ||
const varNames = Object.getOwnPropertyNames(context.vars) | ||
const actualUrl = varNames.reduce((accUrl, name) => { | ||
return accUrl.split(`{{${name}}}`).join(context.vars[name]) | ||
}, url.split(' ').join('')) | ||
|
||
// Get URL info needed to re-write the request. | ||
const targetUrl = new URL(actualUrl) | ||
const endpoint = new aws.Endpoint(targetUrl.href) | ||
const request = new aws.HttpRequest(endpoint) | ||
|
||
// Copy Artillery request parameters into the AWS HttpRequest. | ||
request.method = requestParams.method | ||
request.path = targetUrl.pathname + targetUrl.search | ||
request.region = awsOptions.region | ||
request.headers.Host = endpoint.host | ||
|
||
// Copy Artillery request headers into the AWS HttpRequest. | ||
for (var header in requestParams.headers) { | ||
request.headers[header] = requestParams.headers[header] | ||
} | ||
|
||
// If Artillery request contains a body then copy it. | ||
if (requestParams.body) { | ||
request.body = requestParams.body | ||
} | ||
|
||
// If Artillery request includes json in the body, | ||
// then stringify and copy it into the AWS HttpRequest. | ||
if (requestParams.json) { | ||
request.body = JSON.stringify(requestParams.json) | ||
} | ||
|
||
// Now with all the request parameters copied to the AWS HttpRequest, | ||
// we can use their signer to generate the Authorization header. | ||
const signer = new aws.Signers.V4(request, awsOptions.serviceName) | ||
signer.addAuthorization(awsOptions.credentials, new Date()) | ||
|
||
// Copy the headers from the AWS HttpRequest back to the Artillery requestParams. | ||
// This will now include the necessary Authorization header needed for AWS. | ||
for (header in request.headers) { | ||
requestParams.headers[header] = request.headers[header] | ||
} | ||
|
||
// Allow Artillery to continue. | ||
callback() | ||
} | ||
} | ||
|
||
// Implement plugin to be consumed by Artillery. | ||
const AwsSigV4Plugin = function (scriptConfig, eventEmitter) { | ||
// Collect configuration from the environment and validate. | ||
const credentials = aws.config.credentials | ||
const region = aws.config.region | ||
const sdkConfigurationIsValid = impl.validateSdkConfig(credentials, region) | ||
|
||
if (!sdkConfigurationIsValid) { | ||
throw new Error('Invalid AWS SDK configuration: see messages above. Cannot create AwsSigV4Plugin.') | ||
} | ||
|
||
const serviceName = scriptConfig.plugins[PLUGIN_NAME].serviceName | ||
var sdkCredentials = false | ||
var sdkCredentialsError | ||
var p | ||
|
||
// impl.validateScriptConfig(scriptConfig) | ||
// serviceName = scriptConfig.plugins[PLUGIN_NAME].serviceName | ||
aws.config.getCredentials(function (err) { | ||
if (err) { | ||
sdkCredentialsError = err | ||
} else { | ||
sdkCredentials = true | ||
if (p) { | ||
impl.addAmazonSignatureV4(serviceName, p.requestParams, p.context, p.ee, p.callback) | ||
} | ||
} | ||
}) | ||
|
||
if (!scriptConfig.processor) { | ||
scriptConfig.processor = {} | ||
} | ||
|
||
scriptConfig.processor.addAmazonSignatureV4 = function (requestParams, context, ee, callback) { | ||
if (!sdkCredentials) { | ||
if (sdkCredentialsError) { | ||
console.log([ | ||
messages.sdkConfigInvalidError, | ||
'credentials fetch error. ', | ||
'Ensure the aws-sdk can obtain valid credentials. ', | ||
'Error: ', | ||
sdkCredentialsError.message | ||
].join('')) | ||
} else { | ||
p = { | ||
requestParams: requestParams, | ||
context: context, | ||
ee: ee, | ||
callback: callback | ||
} | ||
}; | ||
} | ||
} else { | ||
impl.addAmazonSignatureV4({ serviceName, region, credentials }, requestParams, context, ee, callback) | ||
} | ||
} | ||
} | ||
|
||
// Provide init() function to return the plugin. | ||
const init = function (scriptConfig, eventEmitter) { | ||
return new AwsSigV4Plugin(scriptConfig, eventEmitter) | ||
} | ||
|
||
module.exports = api.init; | ||
module.exports = init | ||
|
||
/* test-code */ | ||
module.exports.constants = constants; | ||
module.exports.messages = messages; | ||
module.exports.impl = impl; | ||
module.exports.api = api; | ||
module.exports.messages = messages | ||
module.exports.impl = impl | ||
/* end-test-code */ |
Oops, something went wrong.