1aws4 2---- 3 4[![Build Status](https://secure.travis-ci.org/mhart/aws4.png?branch=master)](http://travis-ci.org/mhart/aws4) 5 6A small utility to sign vanilla node.js http(s) request options using Amazon's 7[AWS Signature Version 4](http://docs.amazonwebservices.com/general/latest/gr/signature-version-4.html). 8 9Can also be used [in the browser](./browser). 10 11This signature is supported by nearly all Amazon services, including 12[S3](http://docs.aws.amazon.com/AmazonS3/latest/API/), 13[EC2](http://docs.aws.amazon.com/AWSEC2/latest/APIReference/), 14[DynamoDB](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/API.html), 15[Kinesis](http://docs.aws.amazon.com/kinesis/latest/APIReference/), 16[Lambda](http://docs.aws.amazon.com/lambda/latest/dg/API_Reference.html), 17[SQS](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/), 18[SNS](http://docs.aws.amazon.com/sns/latest/api/), 19[IAM](http://docs.aws.amazon.com/IAM/latest/APIReference/), 20[STS](http://docs.aws.amazon.com/STS/latest/APIReference/), 21[RDS](http://docs.aws.amazon.com/AmazonRDS/latest/APIReference/), 22[CloudWatch](http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/), 23[CloudWatch Logs](http://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/), 24[CodeDeploy](http://docs.aws.amazon.com/codedeploy/latest/APIReference/), 25[CloudFront](http://docs.aws.amazon.com/AmazonCloudFront/latest/APIReference/), 26[CloudTrail](http://docs.aws.amazon.com/awscloudtrail/latest/APIReference/), 27[ElastiCache](http://docs.aws.amazon.com/AmazonElastiCache/latest/APIReference/), 28[EMR](http://docs.aws.amazon.com/ElasticMapReduce/latest/API/), 29[Glacier](http://docs.aws.amazon.com/amazonglacier/latest/dev/amazon-glacier-api.html), 30[CloudSearch](http://docs.aws.amazon.com/cloudsearch/latest/developerguide/APIReq.html), 31[Elastic Load Balancing](http://docs.aws.amazon.com/ElasticLoadBalancing/latest/APIReference/), 32[Elastic Transcoder](http://docs.aws.amazon.com/elastictranscoder/latest/developerguide/api-reference.html), 33[CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/), 34[Elastic Beanstalk](http://docs.aws.amazon.com/elasticbeanstalk/latest/api/), 35[Storage Gateway](http://docs.aws.amazon.com/storagegateway/latest/userguide/AWSStorageGatewayAPI.html), 36[Data Pipeline](http://docs.aws.amazon.com/datapipeline/latest/APIReference/), 37[Direct Connect](http://docs.aws.amazon.com/directconnect/latest/APIReference/), 38[Redshift](http://docs.aws.amazon.com/redshift/latest/APIReference/), 39[OpsWorks](http://docs.aws.amazon.com/opsworks/latest/APIReference/), 40[SES](http://docs.aws.amazon.com/ses/latest/APIReference/), 41[SWF](http://docs.aws.amazon.com/amazonswf/latest/apireference/), 42[AutoScaling](http://docs.aws.amazon.com/AutoScaling/latest/APIReference/), 43[Mobile Analytics](http://docs.aws.amazon.com/mobileanalytics/latest/ug/server-reference.html), 44[Cognito Identity](http://docs.aws.amazon.com/cognitoidentity/latest/APIReference/), 45[Cognito Sync](http://docs.aws.amazon.com/cognitosync/latest/APIReference/), 46[Container Service](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/), 47[AppStream](http://docs.aws.amazon.com/appstream/latest/developerguide/appstream-api-rest.html), 48[Key Management Service](http://docs.aws.amazon.com/kms/latest/APIReference/), 49[Config](http://docs.aws.amazon.com/config/latest/APIReference/), 50[CloudHSM](http://docs.aws.amazon.com/cloudhsm/latest/dg/api-ref.html), 51[Route53](http://docs.aws.amazon.com/Route53/latest/APIReference/requests-rest.html) and 52[Route53 Domains](http://docs.aws.amazon.com/Route53/latest/APIReference/requests-rpc.html). 53 54Indeed, the only AWS services that *don't* support v4 as of 2014-12-30 are 55[Import/Export](http://docs.aws.amazon.com/AWSImportExport/latest/DG/api-reference.html) and 56[SimpleDB](http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/SDB_API.html) 57(they only support [AWS Signature Version 2](https://github.com/mhart/aws2)). 58 59It also provides defaults for a number of core AWS headers and 60request parameters, making it very easy to query AWS services, or 61build out a fully-featured AWS library. 62 63Example 64------- 65 66```javascript 67var http = require('http'), 68 https = require('https'), 69 aws4 = require('aws4') 70 71// given an options object you could pass to http.request 72var opts = {host: 'sqs.us-east-1.amazonaws.com', path: '/?Action=ListQueues'} 73 74// alternatively (as aws4 can infer the host): 75opts = {service: 'sqs', region: 'us-east-1', path: '/?Action=ListQueues'} 76 77// alternatively (as us-east-1 is default): 78opts = {service: 'sqs', path: '/?Action=ListQueues'} 79 80aws4.sign(opts) // assumes AWS credentials are available in process.env 81 82console.log(opts) 83/* 84{ 85 host: 'sqs.us-east-1.amazonaws.com', 86 path: '/?Action=ListQueues', 87 headers: { 88 Host: 'sqs.us-east-1.amazonaws.com', 89 'X-Amz-Date': '20121226T061030Z', 90 Authorization: 'AWS4-HMAC-SHA256 Credential=ABCDEF/20121226/us-east-1/sqs/aws4_request, ...' 91 } 92} 93*/ 94 95// we can now use this to query AWS using the standard node.js http API 96http.request(opts, function(res) { res.pipe(process.stdout) }).end() 97/* 98<?xml version="1.0"?> 99<ListQueuesResponse xmlns="http://queue.amazonaws.com/doc/2012-11-05/"> 100... 101*/ 102``` 103 104More options 105------------ 106 107```javascript 108// you can also pass AWS credentials in explicitly (otherwise taken from process.env) 109aws4.sign(opts, {accessKeyId: '', secretAccessKey: ''}) 110 111// can also add the signature to query strings 112aws4.sign({service: 's3', path: '/my-bucket?X-Amz-Expires=12345', signQuery: true}) 113 114// create a utility function to pipe to stdout (with https this time) 115function request(o) { https.request(o, function(res) { res.pipe(process.stdout) }).end(o.body || '') } 116 117// aws4 can infer the HTTP method if a body is passed in 118// method will be POST and Content-Type: 'application/x-www-form-urlencoded; charset=utf-8' 119request(aws4.sign({service: 'iam', body: 'Action=ListGroups&Version=2010-05-08'})) 120/* 121<ListGroupsResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/"> 122... 123*/ 124 125// can specify any custom option or header as per usual 126request(aws4.sign({ 127 service: 'dynamodb', 128 region: 'ap-southeast-2', 129 method: 'POST', 130 path: '/', 131 headers: { 132 'Content-Type': 'application/x-amz-json-1.0', 133 'X-Amz-Target': 'DynamoDB_20120810.ListTables' 134 }, 135 body: '{}' 136})) 137/* 138{"TableNames":[]} 139... 140*/ 141 142// works with all other services that support Signature Version 4 143 144request(aws4.sign({service: 's3', path: '/', signQuery: true})) 145/* 146<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> 147... 148*/ 149 150request(aws4.sign({service: 'ec2', path: '/?Action=DescribeRegions&Version=2014-06-15'})) 151/* 152<DescribeRegionsResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/"> 153... 154*/ 155 156request(aws4.sign({service: 'sns', path: '/?Action=ListTopics&Version=2010-03-31'})) 157/* 158<ListTopicsResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> 159... 160*/ 161 162request(aws4.sign({service: 'sts', path: '/?Action=GetSessionToken&Version=2011-06-15'})) 163/* 164<GetSessionTokenResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/"> 165... 166*/ 167 168request(aws4.sign({service: 'cloudsearch', path: '/?Action=ListDomainNames&Version=2013-01-01'})) 169/* 170<ListDomainNamesResponse xmlns="http://cloudsearch.amazonaws.com/doc/2013-01-01/"> 171... 172*/ 173 174request(aws4.sign({service: 'ses', path: '/?Action=ListIdentities&Version=2010-12-01'})) 175/* 176<ListIdentitiesResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/"> 177... 178*/ 179 180request(aws4.sign({service: 'autoscaling', path: '/?Action=DescribeAutoScalingInstances&Version=2011-01-01'})) 181/* 182<DescribeAutoScalingInstancesResponse xmlns="http://autoscaling.amazonaws.com/doc/2011-01-01/"> 183... 184*/ 185 186request(aws4.sign({service: 'elasticloadbalancing', path: '/?Action=DescribeLoadBalancers&Version=2012-06-01'})) 187/* 188<DescribeLoadBalancersResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/"> 189... 190*/ 191 192request(aws4.sign({service: 'cloudformation', path: '/?Action=ListStacks&Version=2010-05-15'})) 193/* 194<ListStacksResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/"> 195... 196*/ 197 198request(aws4.sign({service: 'elasticbeanstalk', path: '/?Action=ListAvailableSolutionStacks&Version=2010-12-01'})) 199/* 200<ListAvailableSolutionStacksResponse xmlns="http://elasticbeanstalk.amazonaws.com/docs/2010-12-01/"> 201... 202*/ 203 204request(aws4.sign({service: 'rds', path: '/?Action=DescribeDBInstances&Version=2012-09-17'})) 205/* 206<DescribeDBInstancesResponse xmlns="http://rds.amazonaws.com/doc/2012-09-17/"> 207... 208*/ 209 210request(aws4.sign({service: 'monitoring', path: '/?Action=ListMetrics&Version=2010-08-01'})) 211/* 212<ListMetricsResponse xmlns="http://monitoring.amazonaws.com/doc/2010-08-01/"> 213... 214*/ 215 216request(aws4.sign({service: 'redshift', path: '/?Action=DescribeClusters&Version=2012-12-01'})) 217/* 218<DescribeClustersResponse xmlns="http://redshift.amazonaws.com/doc/2012-12-01/"> 219... 220*/ 221 222request(aws4.sign({service: 'cloudfront', path: '/2014-05-31/distribution'})) 223/* 224<DistributionList xmlns="http://cloudfront.amazonaws.com/doc/2014-05-31/"> 225... 226*/ 227 228request(aws4.sign({service: 'elasticache', path: '/?Action=DescribeCacheClusters&Version=2014-07-15'})) 229/* 230<DescribeCacheClustersResponse xmlns="http://elasticache.amazonaws.com/doc/2014-07-15/"> 231... 232*/ 233 234request(aws4.sign({service: 'elasticmapreduce', path: '/?Action=DescribeJobFlows&Version=2009-03-31'})) 235/* 236<DescribeJobFlowsResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31"> 237... 238*/ 239 240request(aws4.sign({service: 'route53', path: '/2013-04-01/hostedzone'})) 241/* 242<ListHostedZonesResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/"> 243... 244*/ 245 246request(aws4.sign({service: 'appstream', path: '/applications'})) 247/* 248{"_links":{"curie":[{"href":"http://docs.aws.amazon.com/appstream/latest/... 249... 250*/ 251 252request(aws4.sign({service: 'cognito-sync', path: '/identitypools'})) 253/* 254{"Count":0,"IdentityPoolUsages":[],"MaxResults":16,"NextToken":null} 255... 256*/ 257 258request(aws4.sign({service: 'elastictranscoder', path: '/2012-09-25/pipelines'})) 259/* 260{"NextPageToken":null,"Pipelines":[]} 261... 262*/ 263 264request(aws4.sign({service: 'lambda', path: '/2014-11-13/functions/'})) 265/* 266{"Functions":[],"NextMarker":null} 267... 268*/ 269 270request(aws4.sign({service: 'ecs', path: '/?Action=ListClusters&Version=2014-11-13'})) 271/* 272<ListClustersResponse xmlns="http://ecs.amazonaws.com/doc/2014-11-13/"> 273... 274*/ 275 276request(aws4.sign({service: 'glacier', path: '/-/vaults', headers: {'X-Amz-Glacier-Version': '2012-06-01'}})) 277/* 278{"Marker":null,"VaultList":[]} 279... 280*/ 281 282request(aws4.sign({service: 'storagegateway', body: '{}', headers: { 283 'Content-Type': 'application/x-amz-json-1.1', 284 'X-Amz-Target': 'StorageGateway_20120630.ListGateways' 285}})) 286/* 287{"Gateways":[]} 288... 289*/ 290 291request(aws4.sign({service: 'datapipeline', body: '{}', headers: { 292 'Content-Type': 'application/x-amz-json-1.1', 293 'X-Amz-Target': 'DataPipeline.ListPipelines' 294}})) 295/* 296{"hasMoreResults":false,"pipelineIdList":[]} 297... 298*/ 299 300request(aws4.sign({service: 'opsworks', body: '{}', headers: { 301 'Content-Type': 'application/x-amz-json-1.1', 302 'X-Amz-Target': 'OpsWorks_20130218.DescribeStacks' 303}})) 304/* 305{"Stacks":[]} 306... 307*/ 308 309request(aws4.sign({service: 'route53domains', body: '{}', headers: { 310 'Content-Type': 'application/x-amz-json-1.1', 311 'X-Amz-Target': 'Route53Domains_v20140515.ListDomains' 312}})) 313/* 314{"Domains":[]} 315... 316*/ 317 318request(aws4.sign({service: 'kinesis', body: '{}', headers: { 319 'Content-Type': 'application/x-amz-json-1.1', 320 'X-Amz-Target': 'Kinesis_20131202.ListStreams' 321}})) 322/* 323{"HasMoreStreams":false,"StreamNames":[]} 324... 325*/ 326 327request(aws4.sign({service: 'cloudtrail', body: '{}', headers: { 328 'Content-Type': 'application/x-amz-json-1.1', 329 'X-Amz-Target': 'CloudTrail_20131101.DescribeTrails' 330}})) 331/* 332{"trailList":[]} 333... 334*/ 335 336request(aws4.sign({service: 'logs', body: '{}', headers: { 337 'Content-Type': 'application/x-amz-json-1.1', 338 'X-Amz-Target': 'Logs_20140328.DescribeLogGroups' 339}})) 340/* 341{"logGroups":[]} 342... 343*/ 344 345request(aws4.sign({service: 'codedeploy', body: '{}', headers: { 346 'Content-Type': 'application/x-amz-json-1.1', 347 'X-Amz-Target': 'CodeDeploy_20141006.ListApplications' 348}})) 349/* 350{"applications":[]} 351... 352*/ 353 354request(aws4.sign({service: 'directconnect', body: '{}', headers: { 355 'Content-Type': 'application/x-amz-json-1.1', 356 'X-Amz-Target': 'OvertureService.DescribeConnections' 357}})) 358/* 359{"connections":[]} 360... 361*/ 362 363request(aws4.sign({service: 'kms', body: '{}', headers: { 364 'Content-Type': 'application/x-amz-json-1.1', 365 'X-Amz-Target': 'TrentService.ListKeys' 366}})) 367/* 368{"Keys":[],"Truncated":false} 369... 370*/ 371 372request(aws4.sign({service: 'config', body: '{}', headers: { 373 'Content-Type': 'application/x-amz-json-1.1', 374 'X-Amz-Target': 'StarlingDoveService.DescribeDeliveryChannels' 375}})) 376/* 377{"DeliveryChannels":[]} 378... 379*/ 380 381request(aws4.sign({service: 'cloudhsm', body: '{}', headers: { 382 'Content-Type': 'application/x-amz-json-1.1', 383 'X-Amz-Target': 'CloudHsmFrontendService.ListAvailableZones' 384}})) 385/* 386{"AZList":["us-east-1a","us-east-1b","us-east-1c"]} 387... 388*/ 389 390request(aws4.sign({ 391 service: 'swf', 392 body: '{"registrationStatus":"REGISTERED"}', 393 headers: { 394 'Content-Type': 'application/x-amz-json-1.0', 395 'X-Amz-Target': 'SimpleWorkflowService.ListDomains' 396 } 397})) 398/* 399{"domainInfos":[]} 400... 401*/ 402 403request(aws4.sign({ 404 service: 'cognito-identity', 405 body: '{"MaxResults": 1}', 406 headers: { 407 'Content-Type': 'application/x-amz-json-1.1', 408 'X-Amz-Target': 'AWSCognitoIdentityService.ListIdentityPools' 409 } 410})) 411/* 412{"IdentityPools":[]} 413... 414*/ 415 416request(aws4.sign({ 417 service: 'mobileanalytics', 418 path: '/2014-06-05/events', 419 body: JSON.stringify({events:[{ 420 eventType: 'a', 421 timestamp: new Date().toISOString(), 422 session: {}, 423 }]}), 424 headers: { 425 'Content-Type': 'application/json', 426 'X-Amz-Client-Context': JSON.stringify({ 427 client: {client_id: 'a', app_title: 'a'}, 428 custom: {}, 429 env: {platform: 'a'}, 430 services: {}, 431 }), 432 } 433})) 434/* 435(HTTP 202, empty response) 436*/ 437 438// Generate CodeCommit Git access password 439var signer = new aws4.RequestSigner({ 440 service: 'codecommit', 441 host: 'git-codecommit.us-east-1.amazonaws.com', 442 method: 'GIT', 443 path: '/v1/repos/MyAwesomeRepo', 444}) 445var password = signer.getDateTime() + 'Z' + signer.signature() 446``` 447 448API 449--- 450 451### aws4.sign(requestOptions, [credentials]) 452 453This calculates and populates the `Authorization` header of 454`requestOptions`, and any other necessary AWS headers and/or request 455options. Returns `requestOptions` as a convenience for chaining. 456 457`requestOptions` is an object holding the same options that the node.js 458[http.request](http://nodejs.org/docs/latest/api/http.html#http_http_request_options_callback) 459function takes. 460 461The following properties of `requestOptions` are used in the signing or 462populated if they don't already exist: 463 464- `hostname` or `host` (will be determined from `service` and `region` if not given) 465- `method` (will use `'GET'` if not given or `'POST'` if there is a `body`) 466- `path` (will use `'/'` if not given) 467- `body` (will use `''` if not given) 468- `service` (will be calculated from `hostname` or `host` if not given) 469- `region` (will be calculated from `hostname` or `host` or use `'us-east-1'` if not given) 470- `headers['Host']` (will use `hostname` or `host` or be calculated if not given) 471- `headers['Content-Type']` (will use `'application/x-www-form-urlencoded; charset=utf-8'` 472 if not given and there is a `body`) 473- `headers['Date']` (used to calculate the signature date if given, otherwise `new Date` is used) 474 475Your AWS credentials (which can be found in your 476[AWS console](https://portal.aws.amazon.com/gp/aws/securityCredentials)) 477can be specified in one of two ways: 478 479- As the second argument, like this: 480 481```javascript 482aws4.sign(requestOptions, { 483 secretAccessKey: "<your-secret-access-key>", 484 accessKeyId: "<your-access-key-id>", 485 sessionToken: "<your-session-token>" 486}) 487``` 488 489- From `process.env`, such as this: 490 491``` 492export AWS_SECRET_ACCESS_KEY="<your-secret-access-key>" 493export AWS_ACCESS_KEY_ID="<your-access-key-id>" 494export AWS_SESSION_TOKEN="<your-session-token>" 495``` 496 497(will also use `AWS_ACCESS_KEY` and `AWS_SECRET_KEY` if available) 498 499The `sessionToken` property and `AWS_SESSION_TOKEN` environment variable are optional for signing 500with [IAM STS temporary credentials](http://docs.aws.amazon.com/STS/latest/UsingSTS/using-temp-creds.html). 501 502Installation 503------------ 504 505With [npm](http://npmjs.org/) do: 506 507``` 508npm install aws4 509``` 510 511Can also be used [in the browser](./browser). 512 513Thanks 514------ 515 516Thanks to [@jed](https://github.com/jed) for his 517[dynamo-client](https://github.com/jed/dynamo-client) lib where I first 518committed and subsequently extracted this code. 519 520Also thanks to the 521[official node.js AWS SDK](https://github.com/aws/aws-sdk-js) for giving 522me a start on implementing the v4 signature. 523 524