From 180aebd429f4afc3bcf2e370ae683e1b7b23a085 Mon Sep 17 00:00:00 2001 From: Michael Hart Date: Sat, 7 Jan 2023 17:15:32 -0500 Subject: [PATCH] Add extraHeadersToIgnore and extraHeadersToInclude options Fixes #37 for OpenSearch Serverless among others --- README.md | 30 ++++++++++++++++++++++++++++++ aws4.js | 12 ++++++++++-- test/fast.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 67e6753..4e9e66f 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,34 @@ request(aws4.sign({ ... */ +// you can also specify extra headers to ignore during signing +request(aws4.sign({ + host: '07tjusf2h91cunochc.us-east-1.aoss.amazonaws.com', + method: 'PUT', + path: '/my-index', + body: '{"mappings":{}}', + headers: { + 'Content-Type': 'application/json', + 'X-Amz-Content-Sha256': 'UNSIGNED-PAYLOAD' + }, + extraHeadersToIgnore: { + 'content-length': true + } +})) + +// and headers to include that would normally be ignored +request(aws4.sign({ + service: 'mycustomservice', + path: '/whatever', + headers: { + 'Range': 'bytes=200-1000, 2000-6576, 19000-' + }, + extraHeadersToInclude: { + 'range': true + } +})) + + // The raw RequestSigner can be used to generate CodeCommit Git passwords var signer = new aws4.RequestSigner({ service: 'codecommit', @@ -128,6 +156,8 @@ populated if they don't already exist: - `service` (will try to be calculated from `hostname` or `host` if not given) - `region` (will try to be calculated from `hostname` or `host` or use `'us-east-1'` if not given) - `signQuery` (to sign the query instead of adding an `Authorization` header, defaults to false) +- `extraHeadersToIgnore` (an object with lowercase header keys to ignore when signing, eg `{ 'content-length': true }`) +- `extraHeadersToInclude` (an object with lowercase header keys to include when signing, overriding any ignores) - `headers['Host']` (will use `hostname` or `host` or be calculated if not given) - `headers['Content-Type']` (will use `'application/x-www-form-urlencoded; charset=utf-8'` if not given and there is a `body`) diff --git a/aws4.js b/aws4.js index b99b319..b0cfb7c 100644 --- a/aws4.js +++ b/aws4.js @@ -72,6 +72,9 @@ function RequestSigner(request, credentials) { request.hostname = headers.Host || headers.host this.isCodeCommitGit = this.service === 'codecommit' && request.method === 'GIT' + + this.extraHeadersToIgnore = request.extraHeadersToIgnore || Object.create(null) + this.extraHeadersToInclude = request.extraHeadersToInclude || Object.create(null) } RequestSigner.prototype.matchHost = function(host) { @@ -81,7 +84,7 @@ RequestSigner.prototype.matchHost = function(host) { // ES's hostParts are sometimes the other way round, if the value that is expected // to be region equals ‘es’ switch them back // e.g. search-cluster-name-aaaa00aaaa0aaa0aaaaaaa0aaa.us-east-1.es.amazonaws.com - if (hostParts[1] === 'es') + if (hostParts[1] === 'es' || hostParts[1] === 'aoss') hostParts = hostParts.reverse() if (hostParts[1] == 's3') { @@ -305,9 +308,14 @@ RequestSigner.prototype.canonicalHeaders = function() { } RequestSigner.prototype.signedHeaders = function() { + var extraHeadersToInclude = this.extraHeadersToInclude, + extraHeadersToIgnore = this.extraHeadersToIgnore return Object.keys(this.request.headers) .map(function(key) { return key.toLowerCase() }) - .filter(function(key) { return HEADERS_TO_IGNORE[key] == null }) + .filter(function(key) { + return extraHeadersToInclude[key] || + (HEADERS_TO_IGNORE[key] == null && !extraHeadersToIgnore[key]) + }) .sort() .join(';') } diff --git a/test/fast.js b/test/fast.js index f4d889b..171c115 100644 --- a/test/fast.js +++ b/test/fast.js @@ -367,6 +367,49 @@ describe('aws4', function() { }) }) + describe('#sign() with extraHeadersToIgnore', function() { + it('should generate signature correctly', function() { + var opts = aws4.sign({ + host: '07tjusf2h91cunochc.us-east-1.aoss.amazonaws.com', + method: 'PUT', + path: '/my-index', + body: '{"mappings":{}}', + headers: { + Date: date, + 'Content-Type': 'application/json', + 'X-Amz-Content-Sha256': 'UNSIGNED-PAYLOAD', + }, + extraHeadersToIgnore: { + 'content-length': true + }, + }) + opts.headers.Authorization.should.equal( + 'AWS4-HMAC-SHA256 Credential=ABCDEF/20121226/us-east-1/aoss/aws4_request, ' + + 'SignedHeaders=content-type;date;host;x-amz-content-sha256;x-amz-date, ' + + 'Signature=ade8635c05bfa4961bc28be0b0a0fbfd3d64e79feb1862f822ee6a4517417bcd') + }) + }) + + describe('#sign() with extraHeadersToInclude', function() { + it('should generate signature correctly', function() { + var opts = aws4.sign({ + service: 'someservice', + path: '/whatever', + headers: { + Date: date, + 'Range': 'bytes=200-1000, 2000-6576, 19000-', + }, + extraHeadersToInclude: { + 'range': true + }, + }) + opts.headers.Authorization.should.equal( + 'AWS4-HMAC-SHA256 Credential=ABCDEF/20121226/us-east-1/someservice/aws4_request, ' + + 'SignedHeaders=date;host;range;x-amz-date, ' + + 'Signature=8f3eba7a5743091daae62d00ce1c911c018d48f72dbdf180b15abe701718317a') + }) + }) + describe('#signature() with CodeCommit Git access', function() { it('should generate signature correctly', function() { var signer = new RequestSigner({