From 8a74fcc96130211d62a462e9fa3bf5426d4ec718 Mon Sep 17 00:00:00 2001 From: Horace Williams Date: Fri, 14 Oct 2022 17:16:05 -0400 Subject: [PATCH] Add CORS headers, configurable with a CORS_ALLOW_ALL setting --- common/etc/nginx/include/s3gateway.js | 43 ++++++++++++++++++- common/etc/nginx/nginx.conf | 1 + deployments/ecs/cloudformation/s3gateway.yaml | 2 + docs/getting_started.md | 1 + settings.example | 1 + standalone_ubuntu_oss_install.sh | 3 ++ test.sh | 1 + test/docker-compose.yaml | 1 + 8 files changed, 52 insertions(+), 1 deletion(-) diff --git a/common/etc/nginx/include/s3gateway.js b/common/etc/nginx/include/s3gateway.js index b1c3891..1a5ca4e 100644 --- a/common/etc/nginx/include/s3gateway.js +++ b/common/etc/nginx/include/s3gateway.js @@ -34,6 +34,7 @@ var debug = _parseBoolean(process.env['S3_DEBUG']); var allow_listing = _parseBoolean(process.env['ALLOW_DIRECTORY_LIST']) var provide_index_page = _parseBoolean(process.env['PROVIDE_INDEX_PAGE']) var append_slash = _parseBoolean(process.env['APPEND_SLASH_FOR_POSSIBLE_DIRECTORY']) +var cors_allow = _parseBoolean(process.env['CORS_ALLOW_ALL']) var s3_style = process.env['S3_STYLE']; @@ -399,6 +400,41 @@ function _s3DirQueryParams(uriPath, method) { return path; } +/** + * Add CORS headers depending on the cors_allow setting + * + * @param r {Request} HTTP request object + * @return boolean Whether the request should be considered 'complete' after this point. + * this only applys to OPTIONS requests when cors_allow is enabled. After + * this function, these requests will have been served a 204. + */ +function handleCors(r) { + if (!cors_allow) { + return false; + } + + if (r.method === 'OPTIONS') { + r.headersOut['Access-Control-Allow-Origin'] = '*'; + r.headersOut['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'; + // Custom headers and headers various browsers *should* be OK with but aren't + r.headersOut['Access-Control-Allow-Headers'] = 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + // Tell client that this pre-flight info is valid for 20 days + + r.headersOut['Access-Control-Max-Age'] = 1728000; + r.headersOut['Content-Type'] = 'text/plain; charset=utf-8'; + r.headersOut['Content-Length'] = 0; + r.return(204, ''); + return true; + } else if (r.method === 'GET' || r.method === 'POST') { + r.headersOut['Access-Control-Allow-Origin'] ='*'; + r.headersOut['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'; + r.headersOut['Access-Control-Allow-Headers'] = 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + r.headersOut['Access-Control-Expose-Headers'] = 'Content-Length,Content-Range'; + return false; + } + return false; +} + /** * Redirects the request to the appropriate location. If the request is not * a read (GET/HEAD) request, then we reject the request outright by returning @@ -408,6 +444,11 @@ function _s3DirQueryParams(uriPath, method) { */ function redirectToS3(r) { // This is a read-only S3 gateway, so we do not support any other methods + var reqIsCorsOnly = handleCors(r); + if (reqIsCorsOnly) { + return; + } + if (!(r.method === 'GET' || r.method === 'HEAD')) { _debug_log(r, 'Invalid method requested: ' + r.method); r.internalRedirect("@error405"); @@ -839,7 +880,7 @@ function _isDirectory(path) { /** * Parses a string to and returns a boolean value based on its value. If the - * string can't be parsed, this method returns null. + * string can't be parsed, this method returns false. * * @param string {*} value representing a boolean * @returns {boolean} boolean value of string diff --git a/common/etc/nginx/nginx.conf b/common/etc/nginx/nginx.conf index 551b5cd..f17e7b3 100644 --- a/common/etc/nginx/nginx.conf +++ b/common/etc/nginx/nginx.conf @@ -25,6 +25,7 @@ env APPEND_SLASH_FOR_POSSIBLE_DIRECTORY; env PROXY_CACHE_VALID_OK; env PROXY_CACHE_VALID_NOTFOUND; env PROXY_CACHE_VALID_FORBIDDEN; +env CORS_ALLOW_ALL; events { worker_connections 1024; diff --git a/deployments/ecs/cloudformation/s3gateway.yaml b/deployments/ecs/cloudformation/s3gateway.yaml index a64bb2d..67e8fd0 100644 --- a/deployments/ecs/cloudformation/s3gateway.yaml +++ b/deployments/ecs/cloudformation/s3gateway.yaml @@ -241,6 +241,8 @@ Resources: Value: default - Name: S3_DEBUG Value: 'false' + - Name: CORS_ALLOW_ALL + Value: 'false' Image: ghcr.io/nginxinc/nginx-s3-gateway/nginx-oss-s3-gateway:latest-njs-oss LogConfiguration: LogDriver: awslogs diff --git a/docs/getting_started.md b/docs/getting_started.md index a940c25..3edb13f 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -37,6 +37,7 @@ running as a Container or as a Systemd service. * `PROXY_CACHE_VALID_NOTFOUND` - Sets caching time for response code 404 * `PROXY_CACHE_VALID_FORBIDDEN` - Sets caching time for response code 403 * `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 +* `CORS_ALLOW_ALL` - Flag (true/false) - Whether to add CORS headers on GET/POST request and to allow OPTIONS requests. If enabled, this will add CORS headers for "fully open" cross domain requests, meaning all domains are allowed, similar to the settings show in [this example](https://enable-cors.org/server_nginx.html). (default: false) If you are using [AWS instance profile credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html), you will need to omit the `S3_ACCESS_KEY_ID` and `S3_SECRET_KEY` variables from diff --git a/settings.example b/settings.example index 23bf87d..6d013a1 100644 --- a/settings.example +++ b/settings.example @@ -14,3 +14,4 @@ APPEND_SLASH_FOR_POSSIBLE_DIRECTORY=false PROXY_CACHE_VALID_OK=1h PROXY_CACHE_VALID_NOTFOUND=1m PROXY_CACHE_VALID_FORBIDDEN=30s +CORS_ALLOW_ALL=false diff --git a/standalone_ubuntu_oss_install.sh b/standalone_ubuntu_oss_install.sh index bab72ab..a95f748 100644 --- a/standalone_ubuntu_oss_install.sh +++ b/standalone_ubuntu_oss_install.sh @@ -141,6 +141,8 @@ S3_SERVER=${S3_SERVER} S3_STYLE=${S3_STYLE} # Flag (true/false) enabling AWS signatures debug output (default: false) S3_DEBUG=${S3_DEBUG} +# Flag (true/false) enable 'fully open' cross-domain CORS access +CORS_ALLOW_ALL=${CORS_ALLOW_ALL} EOF # Only include these env vars if we are not using a instance profile credential @@ -262,6 +264,7 @@ env AWS_SIGS_VERSION; env S3_DEBUG; env S3_STYLE; env ALLOW_DIRECTORY_LIST; +env CORS_ALLOW_ALL; events { worker_connections 1024; diff --git a/test.sh b/test.sh index 9bb2147..dd8cb63 100755 --- a/test.sh +++ b/test.sh @@ -227,6 +227,7 @@ MSYS_NO_PATHCONV=1 "${docker_cmd}" run \ -e "S3_SERVER_PORT=443" \ -e "S3_REGION=test-1" \ -e "AWS_SIGS_VERSION=4" \ + -e "CORS_ALLOW_ALL=true" \ --entrypoint /usr/bin/njs \ nginx-s3-gateway -t module -p '/etc/nginx' /var/tmp/s3gateway_test.js diff --git a/test/docker-compose.yaml b/test/docker-compose.yaml index 49ef0de..4487c96 100644 --- a/test/docker-compose.yaml +++ b/test/docker-compose.yaml @@ -31,6 +31,7 @@ services: PROXY_CACHE_VALID_OK: "1h" PROXY_CACHE_VALID_NOTFOUND: "1m" PROXY_CACHE_VALID_FORBIDDEN: "30s" + CORS_ALLOW_ALL: "false" minio: image: "minio/minio:RELEASE.2021-02-19T04-38-02Z"