Skip to content

Commit 3052946

Browse files
committed
Add optional setting HEADER_PREFIXES_TO_STRIP
The setting HEADER_PREFIXES_TO_STRIP allows a user to configure which headers will be stripped from the client response. Typically, one will want to remove sensitive headers that might reveal information about the object in the object store. Fixes #65 Signed-off-by: Elijah Zupancic <[email protected]>
1 parent b657c84 commit 3052946

File tree

6 files changed

+168
-30
lines changed

6 files changed

+168
-30
lines changed

common/docker-entrypoint.d/00-check-for-required-env.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ if [ "$(parseBoolean ${ALLOW_DIRECTORY_LIST})" == "1" ] && [ "$(parseBoolean ${P
6666
failed=1
6767
fi
6868

69+
if [ -n "${HEADER_PREFIXES_TO_STRIP+x}" ]; then
70+
if [[ "${HEADER_PREFIXES_TO_STRIP}" =~ [A-Z] ]]; then
71+
>&2 echo "HEADER_PREFIXES_TO_STRIP must not contain uppercase characters"
72+
failed=1
73+
fi
74+
fi
75+
76+
6977
if [ $failed -gt 0 ]; then
7078
exit 1
7179
fi
@@ -80,3 +88,4 @@ echo "DNS Resolvers: ${DNS_RESOLVERS}"
8088
echo "Directory Listing Enabled: ${ALLOW_DIRECTORY_LIST}"
8189
echo "Provide Index Pages Enabled: ${PROVIDE_INDEX_PAGE}"
8290
echo "Append slash for directory enabled: ${APPEND_SLASH_FOR_POSSIBLE_DIRECTORY}"
91+
echo "Stripping the following headers from responses: x-amz-;${HEADER_PREFIXES_TO_STRIP}"

common/etc/nginx/include/s3gateway.js

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const APPEND_SLASH = _parseBoolean(process.env['APPEND_SLASH_FOR_POSSIBLE_DIRECT
3737

3838
const S3_STYLE = process.env['S3_STYLE'];
3939

40+
const ADDITIONAL_HEADER_PREFIXES_TO_STRIP = _parseArray(process.env['HEADER_PREFIXES_TO_STRIP']);
41+
4042
/**
4143
* Default filename for index pages to be read off of the backing object store.
4244
* @type {string}
@@ -87,9 +89,9 @@ const EC2_IMDS_SECURITY_CREDENTIALS_ENDPOINT = 'http://169.254.169.254/latest/me
8789
* leakage about S3 and do other tasks needed for appropriate gateway output.
8890
* @param r HTTP request
8991
*/
90-
function editAmzHeaders(r) {
92+
function editHeaders(r) {
9193
const isDirectoryHeadRequest =
92-
allow_listing &&
94+
ALLOW_LISTING &&
9395
r.method === 'HEAD' &&
9496
_isDirectory(decodeURIComponent(r.variables.uri_path));
9597

@@ -101,7 +103,7 @@ function editAmzHeaders(r) {
101103
* none of the information is relevant for passing on via a gateway. */
102104
if (isDirectoryHeadRequest) {
103105
delete r.headersOut[key];
104-
} else if (key.toLowerCase().indexOf("x-amz-", 0) >= 0) {
106+
} else if (_isHeaderToBeStripped(key.toLowerCase(), ADDITIONAL_HEADER_PREFIXES_TO_STRIP)) {
105107
delete r.headersOut[key];
106108
}
107109
}
@@ -116,6 +118,28 @@ function editAmzHeaders(r) {
116118
}
117119
}
118120

121+
/**
122+
* Determines if a given HTTP header should be removed before being
123+
* sent on to the requesting client.
124+
* @param headerName {string} Lowercase HTTP header name
125+
* @param additionalHeadersToStrip {Array[string]} array of additional headers to remove
126+
* @returns {boolean} true if header should be removed
127+
*/
128+
function _isHeaderToBeStripped(headerName, additionalHeadersToStrip) {
129+
if (headerName.indexOf('x-amz-', 0) >= 0) {
130+
return true;
131+
}
132+
133+
for (let i = 0; i < additionalHeadersToStrip.length; i++) {
134+
const headerToStrip = additionalHeadersToStrip[i];
135+
if (headerName.indexOf(headerToStrip, 0) >= 0) {
136+
return true;
137+
}
138+
}
139+
140+
return false;
141+
}
142+
119143
/**
120144
* Outputs the timestamp used to sign the request, so that it can be added to
121145
* the 'Date' header and sent by NGINX.
@@ -286,7 +310,7 @@ function s3auth(r) {
286310
const bucket = process.env['S3_BUCKET_NAME'];
287311
const region = process.env['S3_REGION'];
288312
let server;
289-
if (s3_style === 'path') {
313+
if (S3_STYLE === 'path') {
290314
server = process.env['S3_SERVER'] + ':' + process.env['S3_SERVER_PORT'];
291315
} else {
292316
server = process.env['S3_SERVER'];
@@ -331,7 +355,7 @@ function s3BaseUri(r) {
331355
const bucket = process.env['S3_BUCKET_NAME'];
332356
let basePath;
333357

334-
if (s3_style === 'path') {
358+
if (S3_STYLE === 'path') {
335359
_debug_log(r, 'Using path style uri : ' + '/' + bucket);
336360
basePath = '/' + bucket;
337361
} else {
@@ -353,7 +377,7 @@ function s3uri(r) {
353377
let path;
354378

355379
// Create query parameters only if directory listing is enabled.
356-
if (allow_listing) {
380+
if (ALLOW_LISTING) {
357381
const queryParams = _s3DirQueryParams(uriPath, r.method);
358382
if (queryParams.length > 0) {
359383
path = basePath + '?' + queryParams;
@@ -362,7 +386,7 @@ function s3uri(r) {
362386
}
363387
} else {
364388
// This is a path that will resolve to an index page
365-
if (provide_index_page && _isDirectory(uriPath) ) {
389+
if (PROVIDE_INDEX_PAGE && _isDirectory(uriPath) ) {
366390
uriPath += INDEX_PAGE;
367391
}
368392
path = _escapeURIPath(basePath + uriPath);
@@ -388,7 +412,7 @@ function _s3DirQueryParams(uriPath, method) {
388412

389413
/* Return if static website. We don't want to list the files in the
390414
directory, we want to append the index page and get the fil. */
391-
if (provide_index_page){
415+
if (PROVIDE_INDEX_PAGE){
392416
return '';
393417
}
394418

@@ -420,21 +444,21 @@ function redirectToS3(r) {
420444
}
421445

422446
const uriPath = r.variables.uri_path;
423-
const isDirectoryListing = allow_listing && _isDirectory(uriPath);
447+
const isDirectoryListing = ALLOW_LISTING && _isDirectory(uriPath);
424448

425449
if (isDirectoryListing && r.method === 'GET') {
426450
r.internalRedirect("@s3Listing");
427-
} else if ( provide_index_page == true ) {
451+
} else if ( PROVIDE_INDEX_PAGE == true ) {
428452
r.internalRedirect("@s3");
429-
} else if ( !allow_listing && !provide_index_page && uriPath == "/" ) {
453+
} else if ( !ALLOW_LISTING && !PROVIDE_INDEX_PAGE && uriPath == "/" ) {
430454
r.internalRedirect("@error404");
431455
} else {
432456
r.internalRedirect("@s3");
433457
}
434458
}
435459

436460
function trailslashControl(r) {
437-
if (append_slash) {
461+
if (APPEND_SLASH) {
438462
const hasExtension = /\/[^.\/]+\.[^.]+$/;
439463
if (!hasExtension.test(r.variables.uri_path) && !_isDirectory(r.variables.uri_path)){
440464
return r.internalRedirect("@trailslash");
@@ -462,7 +486,7 @@ function signatureV2(r, bucket, credentials) {
462486
* Thus, we can't put the path /dir1/ in the string to sign. */
463487
let uri = _isDirectory(r.variables.uri_path) ? '/' : r.variables.uri_path;
464488
// To return index pages + index.html
465-
if (provide_index_page && _isDirectory(r.variables.uri_path)){
489+
if (PROVIDE_INDEX_PAGE && _isDirectory(r.variables.uri_path)){
466490
uri = r.variables.uri_path + INDEX_PAGE
467491
}
468492
const hmac = mod_hmac.createHmac('sha1', credentials.secretAccessKey);
@@ -561,7 +585,7 @@ function signatureV4(r, timestamp, bucket, region, server, credentials) {
561585
*/
562586
function _buildSignatureV4(r, amzDatetime, eightDigitDate, creds, bucket, region, server) {
563587
let host = server;
564-
if (s3_style === 'virtual' || s3_style === 'default' || s3_style === undefined) {
588+
if (S3_STYLE === 'virtual' || S3_STYLE === 'default' || S3_STYLE === undefined) {
565589
host = bucket + '.' + host;
566590
}
567591
const method = r.method;
@@ -865,6 +889,25 @@ function _parseBoolean(string) {
865889
}
866890
}
867891

892+
/**
893+
* Parses a string delimited by semicolons into an array of values
894+
* @param string {string|null} value representing a array of strings
895+
* @returns {Array} a list of values
896+
* @private
897+
*/
898+
function _parseArray(string) {
899+
if (string == null || !string || string === ';') {
900+
return [];
901+
}
902+
903+
// Exclude trailing delimiter
904+
if (string.endsWith(';')) {
905+
return string.substr(0, string.length - 1).split(';');
906+
}
907+
908+
return string.split(';')
909+
}
910+
868911
/**
869912
* Outputs a log message to the request logger if debug messages are enabled.
870913
*
@@ -873,7 +916,7 @@ function _parseBoolean(string) {
873916
* @private
874917
*/
875918
function _debug_log(r, msg) {
876-
if (debug && "log" in r) {
919+
if (DEBUG && "log" in r) {
877920
r.log(msg);
878921
}
879922
}
@@ -1125,7 +1168,7 @@ export default {
11251168
s3uri,
11261169
trailslashControl,
11271170
redirectToS3,
1128-
editAmzHeaders,
1171+
editHeaders,
11291172
filterListResponse,
11301173
// These functions do not need to be exposed, but they are exposed so that
11311174
// unit tests can run against them.
@@ -1136,5 +1179,7 @@ export default {
11361179
_splitCachedValues,
11371180
_buildSigningKeyHash,
11381181
_buildSignatureV4,
1139-
_escapeURIPath
1182+
_escapeURIPath,
1183+
_parseArray,
1184+
_isHeaderToBeStripped
11401185
};

common/etc/nginx/nginx.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ env APPEND_SLASH_FOR_POSSIBLE_DIRECTORY;
2525
env PROXY_CACHE_VALID_OK;
2626
env PROXY_CACHE_VALID_NOTFOUND;
2727
env PROXY_CACHE_VALID_FORBIDDEN;
28+
env HEADER_PREFIXES_TO_STRIP;
2829

2930
events {
3031
worker_connections 1024;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ server {
104104
# We strip off all of the AWS specific headers from the server so that
105105
# there is nothing identifying the object as having originated in an
106106
# object store.
107-
js_header_filter s3gateway.editAmzHeaders;
107+
js_header_filter s3gateway.editHeaders;
108108

109109
# Catch all errors from S3 and sanitize them so that the user can't
110110
# gain intelligence about the S3 bucket being proxied.
@@ -143,7 +143,7 @@ server {
143143
# We strip off all of the AWS specific headers from the server so that
144144
# there is nothing identifying the object as having originated in an
145145
# object store.
146-
js_header_filter s3gateway.editAmzHeaders;
146+
js_header_filter s3gateway.editHeaders;
147147

148148
# Apply XSL transformation to the XML returned from S3 directory listing
149149
# results such that we can output an HTML directory contents list.

docs/getting_started.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ running as a Container or as a Systemd service.
3636
* `PROXY_CACHE_VALID_OK` - Sets caching time for response code 200 and 302
3737
* `PROXY_CACHE_VALID_NOTFOUND` - Sets caching time for response code 404
3838
* `PROXY_CACHE_VALID_FORBIDDEN` - Sets caching time for response code 403
39-
* `JS_TRUSTED_CERT_PATH` - (optional) Enables the `js_fetch_trusted_certificate` directive when retrieving AWS credentials and sets the path (on the container) to the specified path
39+
* `JS_TRUSTED_CERT_PATH` - (optional) Enables the `js_fetch_trusted_certificate` directive when retrieving AWS credentials and sets the path (on the container) to the specified path
40+
* `HEADER_PREFIXES_TO_STRIP` - (optional) a list of HTTP header prefixes that exclude headers client responses. List should be specified in lower-case and a semicolon (;) should be used to as a deliminator between values. For example: `x-goog-;x-something-`
4041

4142
If you are using [AWS instance profile credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html),
4243
you will need to omit the `S3_ACCESS_KEY_ID` and `S3_SECRET_KEY` variables from

0 commit comments

Comments
 (0)