Skip to content

Commit c347fe7

Browse files
committed
Fixes #1
Adds support for signing key caching
1 parent e2ea8a2 commit c347fe7

File tree

6 files changed

+125
-19
lines changed

6 files changed

+125
-19
lines changed

common/etc/nginx/include/s3gateway.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,40 @@ function _buildSignatureV4(r, amzDatetime, eightDigitDate, bucket, secret, regio
203203
r.log('AWS v4 Auth Signing String: [' + stringToSign + ']');
204204
}
205205

206-
var kSigningHash = _buildSigningKeyHash(secret, eightDigitDate, service, region);
206+
var kSigningHash;
207+
208+
/* If we have a keyval zone and key defined for caching the signing key hash,
209+
* then signing key caching will be enabled. By caching signing keys we can
210+
* accelerate the signing process because we will have four less HMAC
211+
* operations that have to be performed per incoming request. The signing
212+
* key expires every day, so our cache key can persist for 24 hours safely.
213+
*/
214+
if ("variables" in r && r.variables.cache_signing_key_enabled == 1) {
215+
// cached value is in the format: [eightDigitDate]:[signingKeyHash]
216+
var cached = "signing_key_hash" in r.variables ? r.variables.signing_key_hash : "";
217+
var fields = cached.split(":", 2);
218+
var cachedEightDigitDate = fields[0];
219+
var cacheIsValid = fields.length === 2 && eightDigitDate === cachedEightDigitDate;
220+
221+
// If true, use cached value
222+
if (cacheIsValid) {
223+
r.log("AWS v4 Using cached Signing Key Hash");
224+
/* We are forced to JSON encode the string returned from the HMAC
225+
* operation because it is in a very specific format that include
226+
* binary data and in order to preserve that data when persisting
227+
* we encode it as JSON. By doing so we can gracefully decode it
228+
* when reading from the cache. */
229+
kSigningHash = JSON.parse(fields[1]);
230+
// Otherwise, generate a new signing key hash and store it in the cache
231+
} else {
232+
kSigningHash = _buildSigningKeyHash(secret, eightDigitDate, service, region);
233+
r.log("Writing key: " + eightDigitDate + ':' + kSigningHash.toString('hex'));
234+
r.variables.signing_key_hash = eightDigitDate + ':' + JSON.stringify(kSigningHash);
235+
}
236+
// Otherwise, don't use caching at all (like when we are using NGINX OSS)
237+
} else {
238+
kSigningHash = _buildSigningKeyHash(secret, eightDigitDate, service, region);
239+
}
207240

208241
if (debug) {
209242
r.log('AWS v4 Signing Key Hash: [' + kSigningHash.toString('hex') + ']');

common/etc/nginx/templates/default.conf.template

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ map $request_uri $uri_path {
1515
}
1616

1717
server {
18+
include /etc/nginx/conf.d/gateway/server_variables.conf;
19+
1820
# Don't display the NGINX version number because we don't want to reveal
1921
# information that could be used to find an exploit.
2022
server_tokens off;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Variable indicating to the s3gateway.js script that singing key
2+
# caching is turned off. This feature uses the keyval store, so it
3+
# is only enabled when using NGINX Plus.
4+
set $cache_signing_key_enabled 0;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Variable indicating to the s3gateway.js script that singing key
2+
# caching is turned on. This feature uses the keyval store, so it
3+
# is only enabled when using NGINX Plus.
4+
set $cache_signing_key_enabled 1;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# This key value zone allows us to cache a portion of the cryptographic
2+
# signatures used by AWS v4 signatures.
3+
keyval_zone zone=aws_signing_cache:32k type=string timeout=24h;
4+
keyval 'aws_signing_key_hash' $signing_key_hash zone=aws_signing_cache;

test/unit/s3gateway_test.js

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ function testAmzDatetime() {
8181
}
8282
}
8383

84-
function testBuildSigningKeyHash() {
84+
function testBuildSigningKeyHashWithReferenceInputs() {
8585
var kSecret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY';
8686
var date = '20150830';
8787
var service = 'iam';
@@ -96,6 +96,43 @@ function testBuildSigningKeyHash() {
9696
}
9797
}
9898

99+
function testBuildSigningKeyHashWithTestSuiteInputs() {
100+
var kSecret = 'pvgoBEA1z7zZKqN9RoKVksKh31AtNou+pspn+iyb';
101+
var date = '20200811';
102+
var service = 's3';
103+
var region = 'us-west-2';
104+
var expected = 'a48701bfe803103e89051f55af2297dd76783bbceb5eb416dab71e0eadcbc4f6';
105+
var signingKeyHash = s3gateway._buildSigningKeyHash(kSecret, date, service, region).toString('hex');
106+
107+
if (signingKeyHash !== expected) {
108+
throw 'Signing key hash was not created correctly.\n' +
109+
'Actual: [' + signingKeyHash + ']\n' +
110+
'Expected: [' + expected + ']';
111+
}
112+
}
113+
114+
function _runSignatureV4(r) {
115+
r.log = function(msg) {
116+
console.log(msg);
117+
}
118+
var timestamp = new Date('2020-08-11T19:42:14Z');
119+
var eightDigitDate = s3gateway._eightDigitDate(timestamp);
120+
var amzDatetime = s3gateway._amzDatetime(timestamp, eightDigitDate);
121+
var bucket = 'ez-test-bucket-1'
122+
var secret = 'pvgoBEA1z7zZKqN9RoKVksKh31AtNou+pspn+iyb'
123+
var region = 'us-west-2';
124+
var server = 's3-us-west-2.amazonaws.com';
125+
126+
var expected = 'cf4dd9e1d28c74e2284f938011efc8230d0c20704f56f67e4a3bfc2212026bec';
127+
var signature = s3gateway._buildSignatureV4(r, amzDatetime, eightDigitDate, bucket, secret, region, server);
128+
129+
if (signature !== expected) {
130+
throw 'V4 signature hash was not created correctly.\n' +
131+
'Actual: [' + signature + ']\n' +
132+
'Expected: [' + expected + ']';
133+
}
134+
}
135+
99136
function testSignatureV4() {
100137
// Note: since this is a read-only gateway, host, query parameters and all
101138
// client headers will be ignored.
@@ -122,33 +159,55 @@ function testSignatureV4() {
122159
"status" : 0
123160
};
124161

125-
r.log = function(msg) {
126-
console.log(msg);
127-
}
128-
var timestamp = new Date('2020-08-11T19:42:14Z');
129-
var eightDigitDate = s3gateway._eightDigitDate(timestamp);
130-
var amzDatetime = s3gateway._amzDatetime(timestamp, eightDigitDate);
131-
var bucket = 'ez-test-bucket-1'
132-
var secret = 'pvgoBEA1z7zZKqN9RoKVksKh31AtNou+pspn+iyb'
133-
var region = 'us-west-2';
134-
var server = 's3-us-west-2.amazonaws.com';
162+
_runSignatureV4(r);
163+
}
135164

136-
var expected = 'cf4dd9e1d28c74e2284f938011efc8230d0c20704f56f67e4a3bfc2212026bec';
137-
var signature = s3gateway._buildSignatureV4(r, amzDatetime, eightDigitDate, bucket, secret, region, server);
165+
function testSignatureV4Cache() {
166+
// Note: since this is a read-only gateway, host, query parameters and all
167+
// client headers will be ignored.
168+
var r = {
169+
"remoteAddress" : "172.17.0.1",
170+
"headersIn" : {
171+
"Connection" : "keep-alive",
172+
"Accept-Encoding" : "gzip, deflate",
173+
"Accept-Language" : "en-US,en;q=0.7,ja;q=0.3",
174+
"Host" : "localhost:8999",
175+
"User-Agent" : "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0",
176+
"DNT" : "1",
177+
"Cache-Control" : "max-age=0",
178+
"Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
179+
"Upgrade-Insecure-Requests" : "1"
180+
},
181+
"uri" : "/a/c/ramen.jpg",
182+
"method" : "GET",
183+
"httpVersion" : "1.1",
184+
"headersOut" : {},
185+
"args" : {
186+
"foo" : "bar"
187+
},
188+
"variables": {
189+
"cache_signing_key_enabled": 1
190+
},
191+
"status" : 0
192+
};
138193

139-
if (signature !== expected) {
140-
throw 'V4 signature hash was not created correctly.\n' +
141-
'Actual: [' + signature + ']\n' +
142-
'Expected: [' + expected + ']';
194+
_runSignatureV4(r);
195+
196+
if (!"signing_key_hash" in r.variables) {
197+
throw "Hash key not written to r.variables.signing_key_hash";
143198
}
199+
200+
_runSignatureV4(r);
144201
}
145202

146203
function test() {
147204
testPad();
148205
testEightDigitDate();
149206
testAmzDatetime();
150-
testBuildSigningKeyHash();
207+
testBuildSigningKeyHashWithReferenceInputs();
208+
testBuildSigningKeyHashWithTestSuiteInputs();
151209
testSignatureV4();
210+
testSignatureV4Cache();
152211
}
153212

154213
test();

0 commit comments

Comments
 (0)