diff --git a/CHANGELOG.md b/CHANGELOG.md index d86685f..0411a19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Version `41` - 2026-06-14 +* Added support for additional trusted proxies via `ADDITIONAL_TRUSTED_PROXIES` by [JackMyers001](https://github.com/mendhak/docker-http-https-echo/pull/101) +* Express update includes the new way of catching all paths + ## Version `40` - 2026-03-20 * Echo back cookies and signed cookies diff --git a/README.md b/README.md index dfcbbd5..92c59a7 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ It comes with various options that can manipulate the response output, see the t ![browser](./screenshots/screenshot.png) -The image is available on [Docker Hub](https://hub.docker.com/r/mendhak/http-https-echo): `mendhak/http-https-echo:40` -The image is available on [Github Container Registry](https://github.com/mendhak/docker-http-https-echo/pkgs/container/http-https-echo): `ghcr.io/mendhak/http-https-echo:40` +The image is available on [Docker Hub](https://hub.docker.com/r/mendhak/http-https-echo): `mendhak/http-https-echo:41` +The image is available on [Github Container Registry](https://github.com/mendhak/docker-http-https-echo/pkgs/container/http-https-echo): `ghcr.io/mendhak/http-https-echo:41` Please do not use the `:latest` tag as it will break without warning, use a specific version instead. @@ -19,6 +19,7 @@ This image is executed as non root by default and is fully compliant with Kubern - [Basic Usage](#basic-usage) - [Choose your ports](#choose-your-ports) - [Use your own certificates](#use-your-own-certificates) +- [Trust additional proxy IPs](#trust-additional-proxy-ips) - [Decode JWT header](#decode-jwt-header) - [Disable ExpressJS log lines](#disable-expressjs-log-lines) - [Do not log specific path](#do-not-log-specific-path) @@ -46,7 +47,7 @@ This image is executed as non root by default and is fully compliant with Kubern Run with Docker - docker run -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 + docker run -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:41 Or run with Docker Compose @@ -63,13 +64,13 @@ You can choose a different internal port instead of 8080 and 8443 with the `HTTP In this example I'm setting http to listen on 8888, and https to listen on 9999. - docker run -e HTTP_PORT=8888 -e HTTPS_PORT=9999 -p 8080:8888 -p 8443:9999 --rm -t mendhak/http-https-echo:40 + docker run -e HTTP_PORT=8888 -e HTTPS_PORT=9999 -p 8080:8888 -p 8443:9999 --rm -t mendhak/http-https-echo:41 With docker compose, this would be: my-http-listener: - image: mendhak/http-https-echo:40 + image: mendhak/http-https-echo:41 environment: - HTTP_PORT=8888 - HTTPS_PORT=9999 @@ -85,7 +86,7 @@ The certificates are at `/app/fullchain.pem` and `/app/testpk.pem`. You can use volume mounting to substitute the certificate and private key with your own. my-http-listener: - image: mendhak/http-https-echo:40 + image: mendhak/http-https-echo:41 ports: - "8080:8080" - "8443:8443" @@ -96,11 +97,18 @@ You can use volume mounting to substitute the certificate and private key with y You can use the environment variables `HTTPS_CERT_FILE` and `HTTPS_KEY_FILE` to define the location of existing certificate and private key inside container. +## Trust additional proxy IPs + +By default, Express is configured to trust forwarded proxy headers from loopback, link-local, and private IP ranges. To also trust additional proxy IPs or subnets, set `ADDITIONAL_TRUSTED_PROXIES` to a single value or a comma-separated list. + + docker run -e ADDITIONAL_TRUSTED_PROXIES="2001:db8::/32,203.0.113.10" -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:41 + + ## Decode JWT header If you specify the header that contains the JWT, the echo output will contain the decoded JWT. Use the `JWT_HEADER` environment variable for this. - docker run -e JWT_HEADER=Authentication -p 8080:8080 -p 8443:8443 --rm -it mendhak/http-https-echo:40 + docker run -e JWT_HEADER=Authentication -p 8080:8080 -p 8443:8443 --rm -it mendhak/http-https-echo:41 Now make your request with `Authentication: eyJ...` header (it should also work with the `Authentication: Bearer eyJ...` schema too): @@ -113,7 +121,7 @@ And in the output you should see a `jwt` section. In the log output set the environment variable `DISABLE_REQUEST_LOGS` to true, to disable the specific ExpressJS request log lines. The ones like `::ffff:172.17.0.1 - - [03/Jan/2022:21:31:51 +0000] "GET /xyz HTTP/1.1" 200 423 "-" "curl/7.68.0"`. The JSON output will still appear. - docker run --rm -e DISABLE_REQUEST_LOGS=true --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 + docker run --rm -e DISABLE_REQUEST_LOGS=true --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:41 ## Do not log specific path @@ -122,12 +130,12 @@ Set the environment variable `LOG_IGNORE_PATH` to a path or a regex you would li This can help reduce noise from healthchecks in orchestration/infrastructure like Swarm, Kubernetes, ALBs, etc. # Ignore a single path - docker run -e LOG_IGNORE_PATH=/ping -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 + docker run -e LOG_IGNORE_PATH=/ping -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:41 # Ignore multiple paths - docker run -e LOG_IGNORE_PATH="^\/ping|^\/health|^\/metrics" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 + docker run -e LOG_IGNORE_PATH="^\/ping|^\/health|^\/metrics" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:41 # Ignore all paths - docker run -e LOG_IGNORE_PATH=".*" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 - docker run -e LOG_IGNORE_PATH="^" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 + docker run -e LOG_IGNORE_PATH=".*" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:41 + docker run -e LOG_IGNORE_PATH="^" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:41 @@ -154,7 +162,7 @@ Will contain a `json` property in the response/output. You can disable new lines in the log output by setting the environment variable `LOG_WITHOUT_NEWLINE`. For example, ```bash -docker run -e LOG_WITHOUT_NEWLINE=true -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 +docker run -e LOG_WITHOUT_NEWLINE=true -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:41 ``` ## Send an empty response @@ -162,7 +170,7 @@ docker run -e LOG_WITHOUT_NEWLINE=true -p 8080:8080 -p 8443:8443 --rm -t mendhak You can disable the JSON output in the response by setting the environment variable `ECHO_BACK_TO_CLIENT`. For example, ```bash -docker run -e ECHO_BACK_TO_CLIENT=false -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 +docker run -e ECHO_BACK_TO_CLIENT=false -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:41 ``` ## Custom status code @@ -243,7 +251,7 @@ You can have environment variables (that are visible to the echo server's proces Pass the `ECHO_INCLUDE_ENV_VARS=1` environment variable in. ```bash -docker run -d --rm -e ECHO_INCLUDE_ENV_VARS=1 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 +docker run -d --rm -e ECHO_INCLUDE_ENV_VARS=1 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:41 ``` Then do a normal request via curl or browser, and you will see the `env` property in the response body. @@ -283,7 +291,7 @@ openssl pkcs12 -export -in cert.pem -inkey testpk.pem -out certpkcs12.pfx By default, the headers in the response body are lowercased. To attempt to preserve the case of headers in the response body, set the environment variable `PRESERVE_HEADER_CASE` to true. ```bash -docker run -e PRESERVE_HEADER_CASE=true -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 +docker run -e PRESERVE_HEADER_CASE=true -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:41 ``` ## Override the response body with a file @@ -292,7 +300,7 @@ To override the response body with a file, set the environment variable `OVERRID The file path needs to be in the `/app` directory. ```bash -docker run -d --rm -v ${PWD}/test.html:/app/test.html -p 8080:8080 -e OVERRIDE_RESPONSE_BODY_FILE_PATH=/test.html -t mendhak/http-https-echo:40 +docker run -d --rm -v ${PWD}/test.html:/app/test.html -p 8080:8080 -e OVERRIDE_RESPONSE_BODY_FILE_PATH=/test.html -t mendhak/http-https-echo:41 ``` ## Set a maximum header size @@ -300,7 +308,7 @@ docker run -d --rm -v ${PWD}/test.html:/app/test.html -p 8080:8080 -e OVERRIDE_R You can use the `MAX_HEADER_SIZE` environment variable to set a maximum header size in bytes. The default is 1MB. ```bash -docker run -d --rm -e MAX_HEADER_SIZE=1000 -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 +docker run -d --rm -e MAX_HEADER_SIZE=1000 -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:41 ``` ## Cookies and Signed Cookies @@ -308,7 +316,7 @@ docker run -d --rm -e MAX_HEADER_SIZE=1000 -p 8080:8080 -p 8443:8443 -t mendhak Make a request with a `Cookie` header and the response will include the cookies: ```bash -docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 +docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:41 ``` Then make a request with a cookie header: @@ -320,7 +328,7 @@ curl -s http://localhost:8080/ -H "Cookie: foo=bar; baz=qux" To enable signed cookie support, set the `COOKIE_SECRET` environment variable. Signed cookies appear in the `signedCookies` section of the response: ```bash -docker run -d --rm -e COOKIE_SECRET=mysecretkey123 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 +docker run -d --rm -e COOKIE_SECRET=mysecretkey123 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:41 ``` Now you need to generate a signed cookie, and send it in the header. Here's a convenience node snippet: diff --git a/docker-compose.yml b/docker-compose.yml index 2e4fb1d..3250591 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3' services: my-http-listener: - image: mendhak/http-https-echo:40 + image: mendhak/http-https-echo:41 environment: - HTTP_PORT=8888 - HTTPS_PORT=9999 diff --git a/index.js b/index.js index a7b92c6..006d49e 100644 --- a/index.js +++ b/index.js @@ -17,11 +17,18 @@ const { PROMETHEUS_WITH_METHOD = 'true', PROMETHEUS_WITH_STATUS = 'true', PROMETHEUS_METRIC_TYPE = 'summary', - MAX_HEADER_SIZE = 1048576 + MAX_HEADER_SIZE = 1048576, + ADDITIONAL_TRUSTED_PROXIES } = process.env const maxHeaderSize = parseInt(MAX_HEADER_SIZE, 10) || 1048576; +const trustProxy = [ + 'loopback', + 'linklocal', + 'uniquelocal', + ...(ADDITIONAL_TRUSTED_PROXIES || '').split(',').map(proxy => proxy.trim()).filter(Boolean) +]; const sleep = promisify(setTimeout); const metricsMiddleware = promBundle({ @@ -34,7 +41,7 @@ const metricsMiddleware = promBundle({ const app = express() app.set('json spaces', 2); -app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']); +app.set('trust proxy', trustProxy); if(PROMETHEUS_ENABLED === 'true') { app.use(metricsMiddleware); diff --git a/package-lock.json b/package-lock.json index 9774eab..d6b2533 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "express": "^5.2.1", "express-prom-bundle": "^8.0.0", "jsonwebtoken": "^9.0.0", - "morgan": "^1.10.1" + "morgan": "^1.11.0" }, "engines": { "node": ">=16.0.0" @@ -859,31 +859,23 @@ } }, "node_modules/morgan": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", - "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.11.0.tgz", + "integrity": "sha512-zSkVu3t18r39pw4ixfBKvfZi3y2UOqr7d4WYwcj3m8nXpEQK4rPO6GLzs/CExoRgmX3y9EjmmcXqv6jq0SK46g==", "license": "MIT", "dependencies": { "basic-auth": "~2.0.1", "debug": "2.6.9", "depd": "~2.0.0", - "on-finished": "~2.3.0", + "on-finished": "~2.4.1", "on-headers": "~1.1.0" }, "engines": { "node": ">= 0.8.0" - } - }, - "node_modules/morgan/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" }, - "engines": { - "node": ">= 0.8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ms": { @@ -990,9 +982,9 @@ } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" diff --git a/package.json b/package.json index 927f2ff..2fb0627 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "express": "^5.2.1", "express-prom-bundle": "^8.0.0", "jsonwebtoken": "^9.0.0", - "morgan": "^1.10.1" + "morgan": "^1.11.0" }, "overrides": { "jws": "^3.2.3" diff --git a/tests.sh b/tests.sh index c6fa41d..d942fa1 100755 --- a/tests.sh +++ b/tests.sh @@ -225,7 +225,7 @@ fi message " Make request with a header exceeding limit." LARGE_HEADER_VALUE=$(head -c 5000 &1) +REQUEST=$(curl -v -k -H "Large-Header: $LARGE_HEADER_VALUE" https://localhost:8443/ 2>&1 || true) if echo "$REQUEST" | grep -q "HTTP/1.1 431 Request Header Fields Too Large"; then passed "Large header test resulted in HTTP 431." else @@ -237,6 +237,22 @@ fi message " Stop containers " stop_and_remove +message " Start container with additional trusted proxies " +docker run -d --rm -e ADDITIONAL_TRUSTED_PROXIES="2001:db8::/32,198.51.100.0/24" --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready + +REQUEST=$(curl -s -H "X-Forwarded-For: 203.0.113.10, 198.51.100.1, 2001:db8::1" http://localhost:8080/) +if [[ "$(echo "$REQUEST" | jq -r '.ip')" == '203.0.113.10' ]]; then + passed "Additional trusted proxies passed." +else + failed "Additional trusted proxies failed." + echo "$REQUEST" | jq + exit 1 +fi + +message " Stop containers " +stop_and_remove + message " Start container with different internal ports " docker run -d --rm -e HTTP_PORT=8888 -e HTTPS_PORT=9999 --name http-echo-tests -p 8080:8888 -p 8443:9999 -t mendhak/http-https-echo:testing wait_for_ready