From 28824add8e793df25ec76a1ac0c38f908841ddcb Mon Sep 17 00:00:00 2001 From: Elijah Zupancic Date: Tue, 29 Nov 2022 10:50:48 -0800 Subject: [PATCH] Fixes #62 This commit adds support for CORS response headers. Signed-off-by: Elijah Zupancic --- Dockerfile.buildkit.plus | 5 +++ Dockerfile.oss | 1 + Dockerfile.plus | 1 + .../00-check-for-required-env.sh | 4 ++- common/docker-entrypoint.sh | 28 ++++++++++++++++- .../etc/nginx/templates/default.conf.template | 21 ++++++++++++- .../templates/gateway/cors.conf.template | 31 +++++++++++++++++++ docs/getting_started.md | 9 +++++- 8 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 common/etc/nginx/templates/gateway/cors.conf.template diff --git a/Dockerfile.buildkit.plus b/Dockerfile.buildkit.plus index 019e067e..16c48fad 100644 --- a/Dockerfile.buildkit.plus +++ b/Dockerfile.buildkit.plus @@ -9,6 +9,11 @@ ENV PROXY_CACHE_VALID_OK "1h" ENV PROXY_CACHE_VALID_NOTFOUND "1m" ENV PROXY_CACHE_VALID_FORBIDDEN "30s" +ENV PROXY_CACHE_VALID_OK "1h" +ENV PROXY_CACHE_VALID_NOTFOUND "1m" +ENV PROXY_CACHE_VALID_FORBIDDEN "30s" +ENV CORS_ENABLED 0 + COPY plus/usr /usr # Copy files from the OSS NGINX Docker container such that the container diff --git a/Dockerfile.oss b/Dockerfile.oss index a62c0dab..a2e287b4 100644 --- a/Dockerfile.oss +++ b/Dockerfile.oss @@ -6,6 +6,7 @@ ENV NJS_VERSION 0.7.9 ENV PROXY_CACHE_VALID_OK "1h" ENV PROXY_CACHE_VALID_NOTFOUND "1m" ENV PROXY_CACHE_VALID_FORBIDDEN "30s" +ENV CORS_ENABLED 0 # We modify the nginx base image by: # 1. Adding configuration files needed for proxying private S3 buckets diff --git a/Dockerfile.plus b/Dockerfile.plus index ca0516a1..0e8de37f 100644 --- a/Dockerfile.plus +++ b/Dockerfile.plus @@ -8,6 +8,7 @@ ENV XSLT_VERSION 28-1 ENV PROXY_CACHE_VALID_OK "1h" ENV PROXY_CACHE_VALID_NOTFOUND "1m" ENV PROXY_CACHE_VALID_FORBIDDEN "30s" +ENV CORS_ENABLED 0 COPY plus/etc/ssl /etc/ssl COPY plus/usr /usr diff --git a/common/docker-entrypoint.d/00-check-for-required-env.sh b/common/docker-entrypoint.d/00-check-for-required-env.sh index 62d75047..f0b0f3ce 100755 --- a/common/docker-entrypoint.d/00-check-for-required-env.sh +++ b/common/docker-entrypoint.d/00-check-for-required-env.sh @@ -23,7 +23,8 @@ set -e failed=0 required=("S3_BUCKET_NAME" "S3_SERVER" "S3_SERVER_PORT" "S3_SERVER_PROTO" -"S3_REGION" "S3_STYLE" "ALLOW_DIRECTORY_LIST" "AWS_SIGS_VERSION") +"S3_REGION" "S3_STYLE" "ALLOW_DIRECTORY_LIST" "AWS_SIGS_VERSION" +"CORS_ENABLED") # Require some form of authentication to be configured. @@ -107,3 +108,4 @@ echo "Directory Listing Enabled: ${ALLOW_DIRECTORY_LIST}" echo "Provide Index Pages Enabled: ${PROVIDE_INDEX_PAGE}" echo "Append slash for directory enabled: ${APPEND_SLASH_FOR_POSSIBLE_DIRECTORY}" echo "Stripping the following headers from responses: x-amz-;${HEADER_PREFIXES_TO_STRIP}" +echo "CORS Enabled: ${CORS_ENABLED}" diff --git a/common/docker-entrypoint.sh b/common/docker-entrypoint.sh index 76b6be8e..580cd603 100644 --- a/common/docker-entrypoint.sh +++ b/common/docker-entrypoint.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash # # Copyright 2020 F5 Networks # @@ -19,11 +19,37 @@ set -e +parseBoolean() { + case "$1" in + TRUE | true | True | YES | Yes | 1) + echo 1 + ;; + *) + echo 0 + ;; + esac +} + # This line is an addition to the NGINX Docker image's entrypoint script. if [ -z ${DNS_RESOLVERS+x} ]; then export DNS_RESOLVERS="$(cat /etc/resolv.conf | grep nameserver | cut -d' ' -f2 | xargs)" fi +# Normalize the CORS_ENABLED environment variable to a numeric value +# so that it can be easily parsed in the nginx configuration. +export CORS_ENABLED="$(parseBoolean "${CORS_ENABLED}")" + +# By enabling CORS, we also need to enable the OPTIONS method which +# is not normally used as part of the gateway. The following variable +# defines the set of acceptable headers. +if [ "${CORS_ENABLED}" == "1" ]; then + export LIMIT_METHODS_TO="GET HEAD OPTIONS" + export LIMIT_METHODS_TO_CSV="GET, HEAD, OPTIONS" +else + export LIMIT_METHODS_TO="GET HEAD" + export LIMIT_METHODS_TO_CSV="GET, HEAD" +fi + # Nothing is modified under this line if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then diff --git a/common/etc/nginx/templates/default.conf.template b/common/etc/nginx/templates/default.conf.template index 5943c94e..898fa142 100644 --- a/common/etc/nginx/templates/default.conf.template +++ b/common/etc/nginx/templates/default.conf.template @@ -66,6 +66,17 @@ server { } location / { + # This value is templated in based on the value of $CORS_ENABLED. When + # CORS is enabled, acceptable methods are GET, HEAD, and OPTIONS. + # Otherwise, they are GET and HEAD. + limit_except ${LIMIT_METHODS_TO} {} + + # CORS is implemented by returning the appropriate headers as part of + # the response to an OPTIONS request. If you want to customize the + # CORS response, the cors.conf.template file can be overwritten and + # extended to meet one's needs. + include /etc/nginx/conf.d/gateway/cors.conf; + auth_request /aws/credentials/retrieve; # Redirect to the proper location based on the client request - either @@ -86,6 +97,10 @@ server { # we plan to use. include /etc/nginx/conf.d/gateway/v${AWS_SIGS_VERSION}_headers.conf; + # The CORS configuration needs to be imported in several places in order for + # it to be applied within different contexts. + include /etc/nginx/conf.d/gateway/cors.conf; + # Don't allow any headers from the client - we don't want them messing # with S3 at all. proxy_pass_request_headers off; @@ -128,6 +143,10 @@ server { # we plan to use. include /etc/nginx/conf.d/gateway/v${AWS_SIGS_VERSION}_headers.conf; + # The CORS configuration needs to be imported in several places in order for + # it to be applied within different contexts. + include /etc/nginx/conf.d/gateway/cors.conf; + # Don't allow any headers from the client - we don't want them messing # with S3 at all. proxy_pass_request_headers off; @@ -193,7 +212,7 @@ server { # Provide a hint to the client on 405 errors of the acceptable request methods error_page 405 @error405; location @error405 { - add_header Allow "GET, HEAD" always; + add_header Allow "${LIMIT_METHODS_TO_CSV}" always; return 405; } diff --git a/common/etc/nginx/templates/gateway/cors.conf.template b/common/etc/nginx/templates/gateway/cors.conf.template new file mode 100644 index 00000000..d5d25bca --- /dev/null +++ b/common/etc/nginx/templates/gateway/cors.conf.template @@ -0,0 +1,31 @@ +set $request_cors "${request_method}_${CORS_ENABLED}"; + +if ($request_cors = "OPTIONS_1") { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + # + # Custom headers and headers various browsers *should* be OK with but aren't + # + add_header '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 + # + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; +} + +if ($request_cors = "GET_1") { + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; +} + +if ($request_cors = "HEAD_1") { + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; +} diff --git a/docs/getting_started.md b/docs/getting_started.md index 3032bd79..361c1799 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -37,7 +37,14 @@ 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 -* `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-` +* `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-` +* `CORS_ENABLED` - Flag (true/false) that enables CORS headers on GET + requests and enables pre-flight OPTIONS requests. If enabled, this will + add CORS headers for "fully open" cross domain requests by default, + meaning all domains are allowed, similar to the settings show in + [this example](https://enable-cors.org/server_nginx.html). + CORS settings can be fine-tuned by overwriting the + [`cors.conf.template`](/common/etc/nginx/templates/gateway/cors.conf.template) file. (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