diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..7ee231c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 10 + groups: + production-dependencies: + patterns: + - "*" 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 2e720a1..a62686a 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:39` -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:39` +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` 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) @@ -30,10 +31,12 @@ This image is executed as non root by default and is fully compliant with Kubern - [Add a delay before response](#add-a-delay-before-response) - [Only return body in the response](#only-return-body-in-the-response) - [Include environment variables in the response](#include-environment-variables-in-the-response) -- [Configuring CORS policy](#setting-corscross-origin-resource-sharing-headers-in-the-response) +- [Setting CORS(Cross-Origin Resource Sharing) headers in the response](#setting-corscross-origin-resource-sharing-headers-in-the-response) - [Client certificate details (mTLS) in the response](#client-certificate-details-mtls-in-the-response) - [Preserve the case of headers in response body](#preserve-the-case-of-headers-in-response-body) - [Override the response body with a file](#override-the-response-body-with-a-file) +- [Set a maximum header size](#set-a-maximum-header-size) +- [Cookies and Signed Cookies](#cookies-and-signed-cookies) - [Prometheus Metrics](#prometheus-metrics) - [Screenshots](#screenshots) - [Building](#building) @@ -44,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:39 + docker run -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 Or run with Docker Compose @@ -61,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:39 + docker run -e HTTP_PORT=8888 -e HTTPS_PORT=9999 -p 8080:8888 -p 8443:9999 --rm -t mendhak/http-https-echo:40 With docker compose, this would be: my-http-listener: - image: mendhak/http-https-echo:39 + image: mendhak/http-https-echo:40 environment: - HTTP_PORT=8888 - HTTPS_PORT=9999 @@ -83,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:39 + image: mendhak/http-https-echo:40 ports: - "8080:8080" - "8443:8443" @@ -94,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:40 + + ## 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:39 + docker run -e JWT_HEADER=Authentication -p 8080:8080 -p 8443:8443 --rm -it mendhak/http-https-echo:40 Now make your request with `Authentication: eyJ...` header (it should also work with the `Authentication: Bearer eyJ...` schema too): @@ -111,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:39 + docker run --rm -e DISABLE_REQUEST_LOGS=true --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 ## Do not log specific path @@ -120,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:39 + docker run -e LOG_IGNORE_PATH=/ping -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 # Ignore multiple paths - docker run -e LOG_IGNORE_PATH="^\/ping|^\/health|^\/metrics" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:39 + docker run -e LOG_IGNORE_PATH="^\/ping|^\/health|^\/metrics" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 # Ignore all paths - docker run -e LOG_IGNORE_PATH=".*" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:39 - docker run -e LOG_IGNORE_PATH="^" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:39 + 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 @@ -152,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:39 +docker run -e LOG_WITHOUT_NEWLINE=true -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 ``` ## Send an empty response @@ -160,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:39 +docker run -e ECHO_BACK_TO_CLIENT=false -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 ``` ## Custom status code @@ -241,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:39 +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 ``` Then do a normal request via curl or browser, and you will see the `env` property in the response body. @@ -281,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:39 +docker run -e PRESERVE_HEADER_CASE=true -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 ``` ## Override the response body with a file @@ -290,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:39 +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 ``` ## Set a maximum header size @@ -298,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:39 +docker run -d --rm -e MAX_HEADER_SIZE=1000 -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 ``` ## Cookies and Signed Cookies @@ -306,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:39 +docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 ``` Then make a request with a cookie header: @@ -318,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:39 +docker run -d --rm -e COOKIE_SECRET=mysecretkey123 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 ``` 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 699ce4f..2e4fb1d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3' services: my-http-listener: - image: mendhak/http-https-echo:39 + image: mendhak/http-https-echo:40 environment: - HTTP_PORT=8888 - HTTPS_PORT=9999 diff --git a/index.js b/index.js index 5876532..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); @@ -58,8 +65,8 @@ app.use(function(req, res, next){ next(); })); }); -//Handle all paths -app.all('*', (req, res) => { +//Handle all paths. This /{*splat} is new Express 5 path syntax. https://expressjs.com/en/guide/migrating-5.html#path-syntax +app.all('/{*splat}', (req, res) => { if(process.env.OVERRIDE_RESPONSE_BODY_FILE_PATH){ // Path is relative to current directory diff --git a/package-lock.json b/package-lock.json index 36b0fd9..d6b2533 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,10 @@ "dependencies": { "concat-stream": "^2.0.0", "cookie-parser": "^1.4.6", - "express": "^4.22.0", + "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" @@ -119,24 +119,18 @@ } }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -163,29 +157,52 @@ "peer": true }, "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -252,15 +269,16 @@ } }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -301,10 +319,13 @@ "license": "MIT" }, "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/debug": { "version": "2.6.9", @@ -324,16 +345,6 @@ "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -418,45 +429,42 @@ } }, "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" }, "funding": { "type": "opencollective", @@ -480,24 +488,73 @@ "prom-client": ">=15.0.0" } }, + "node_modules/express/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, + "node_modules/finalhandler/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -508,12 +565,12 @@ } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/function-bind": { @@ -619,15 +676,19 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/inherits": { @@ -645,6 +706,12 @@ "node": ">= 0.10" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/jsonwebtoken": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", @@ -746,91 +813,69 @@ } }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" + "node": ">=18" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "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": { @@ -840,9 +885,9 @@ "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -881,6 +926,15 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -891,10 +945,14 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/prom-client": { "version": "15.1.3", @@ -924,9 +982,9 @@ } }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "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" @@ -948,18 +1006,18 @@ } }, "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", + "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, "node_modules/readable-stream": { @@ -976,6 +1034,45 @@ "node": ">= 6" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1015,27 +1112,46 @@ } }, "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/send/node_modules/ms": { @@ -1045,18 +1161,22 @@ "license": "MIT" }, "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { @@ -1175,13 +1295,14 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { "node": ">= 0.6" @@ -1223,15 +1344,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1240,6 +1352,12 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" } } } diff --git a/package.json b/package.json index 2fd41c2..2fb0627 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,10 @@ "dependencies": { "concat-stream": "^2.0.0", "cookie-parser": "^1.4.6", - "express": "^4.22.0", + "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 73cca7f..d942fa1 100755 --- a/tests.sh +++ b/tests.sh @@ -5,7 +5,7 @@ set -euo pipefail function message { echo "" echo "---------------------------------------------------------------" - echo $1 + echo "$1" echo "---------------------------------------------------------------" } @@ -14,23 +14,55 @@ RED=$(echo -en '\033[01;31m') GREEN=$(echo -en '\033[01;32m') function failed { - echo ${RED}✗$1${RESTORE} + echo "${RED}✗${1}${RESTORE}" } function passed { - echo ${GREEN}✓$1${RESTORE} + echo "${GREEN}✓${1}${RESTORE}" } -if ! [ -x "$(command -v jq)" ]; then +wait_for_ready() { + local check_path="${1:-/}" + for i in {1..20}; do + if curl -sf "http://localhost:8080${check_path}" >/dev/null 2>&1; then + return 0 + fi + sleep 0.5 + done + echo "Container failed to start"; exit 1 +} + +stop_and_remove() { + docker stop http-echo-tests 2>/dev/null || true + for i in {1..20}; do + if ! docker ps -aq --filter "name=http-echo-tests" | grep -q .; then + return 0 + fi + sleep 0.5 + done + echo "Container failed to stop"; exit 1 +} + +cleanup() { + echo "Cleaning up..." + docker stop http-echo-tests 2>/dev/null || true + docker rm http-echo-tests 2>/dev/null || true + popd 2>/dev/null || true + rm -rf testarea +} + +trap cleanup EXIT + +if ! command -v jq >/dev/null 2>&1; then message "JQ not installed. Installing..." sudo apt -y install jq fi message " Check if we're in Github Actions or local run " -if [ -n "${GITHUB_ACTIONS:-}" ]; then +if [[ -n "${GITHUB_ACTIONS:-}" ]]; then echo " Github Actions. Image should already be built." docker images - if [ -z "$(docker images -q mendhak/http-https-echo:testing 2> /dev/null)" ]; then + if [[ -z "$(docker images -q mendhak/http-https-echo:testing 2> /dev/null)" ]]; then echo "Docker image mendhak/http-https-echo:testing not found. Exiting." exit 1 fi @@ -44,45 +76,43 @@ mkdir -p testarea pushd testarea message " Cleaning up from previous test run " -docker ps -aq --filter "name=http-echo-tests" | grep -q . && docker stop http-echo-tests && docker rm -f http-echo-tests +docker rm -f http-echo-tests 2>/dev/null || true +stop_and_remove message " Start container normally " docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 10 +wait_for_ready message " Make http(s) request, and test the path, method, header and status code. " REQUEST=$(curl -s -k -X PUT -H "Arbitrary:Header" -d aaa=bbb 'https://localhost:8443/hello-world?ccc=ddd&myquery=98765') -if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ - [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ - [ $(echo $REQUEST | jq -r '.query.myquery') == '98765' ] && \ - [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] -then +if [[ "$(echo "$REQUEST" | jq -r '.path')" == '/hello-world' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.method')" == 'PUT' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.query.myquery')" == '98765' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.headers.arbitrary')" == 'Header' ]]; then passed "HTTPS request passed." else failed "HTTPS request failed." - echo $REQUEST | jq + echo "$REQUEST" | jq exit 1 fi REQUEST_WITH_STATUS_CODE=$(curl -s -k -o /dev/null -w "%{http_code}" -H "x-set-response-status-code: 404" https://localhost:8443/hello-world) REQUEST_WITH_STATUS_CODE_V=$(curl -v -k -o /dev/null -w "%{http_code}" -H "x-set-response-status-code: 404" https://localhost:8443/hello-world) -if [ $(echo $REQUEST_WITH_STATUS_CODE == '404') ] -then +if [[ "$REQUEST_WITH_STATUS_CODE" == "404" ]]; then passed "HTTPS status code header passed." else failed "HTTPS status code header failed." - echo $REQUEST_WITH_STATUS_CODE_V + echo "$REQUEST_WITH_STATUS_CODE_V" exit 1 fi REQUEST_WITH_STATUS_CODE=$(curl -s -k -o /dev/null -w "%{http_code}" https://localhost:8443/status/test?x-set-response-status-code=419) REQUEST_WITH_STATUS_CODE_V=$(curl -v -k -o /dev/null -w "%{http_code}" https://localhost:8443/hello-world?x-set-response-status-code=419) -if [ $(echo $REQUEST_WITH_STATUS_CODE == '419') ] -then +if [[ "$REQUEST_WITH_STATUS_CODE" == "419" ]]; then passed "HTTPS status code querystring passed." else failed "HTTPS status code querystring failed." - echo $REQUEST_WITH_STATUS_CODE_V + echo "$REQUEST_WITH_STATUS_CODE_V" exit 1 fi @@ -90,7 +120,7 @@ REQUEST_WITH_CONTENT_TYPE_HEADER=$(curl -o /dev/null -k -Ss -w "%{content_type}" if [[ "$REQUEST_WITH_CONTENT_TYPE_HEADER" == *"aaaa/bbbb"* ]]; then passed "Request with custom response type header, passed" else - echo $REQUEST_WITH_CONTENT_TYPE_HEADER + echo "$REQUEST_WITH_CONTENT_TYPE_HEADER" failed "Request with custom response type header, failed." exit 1 fi @@ -99,7 +129,7 @@ REQUEST_WITH_CONTENT_TYPE_PARAMETER=$(curl -o /dev/null -k -Ss -w "%{content_typ if [[ "$REQUEST_WITH_CONTENT_TYPE_PARAMETER" == *"jellyfish/cabbage"* ]]; then passed "Request with custom response type parameter, passed" else - echo $REQUEST_WITH_CONTENT_TYPE_PARAMETER + echo "$REQUEST_WITH_CONTENT_TYPE_PARAMETER" failed "Request with custom response type parameter, failed." exit 1 fi @@ -110,7 +140,7 @@ if [[ $(echo "$REQUEST_WITH_SLEEP_MS>5" | bc -l) == 1 ]]; then passed "Request header with response delay passed" else failed "Request header with response delay failed" - echo $REQUEST_WITH_SLEEP_MS + echo "$REQUEST_WITH_SLEEP_MS" exit 1 fi @@ -119,7 +149,7 @@ if [[ $(echo "$REQUEST_WITH_SLEEP_MS>4" | bc -l) == 1 ]]; then passed "Request query with response delay passed" else failed "Request query with response delay failed" - echo $REQUEST_WITH_SLEEP_MS + echo "$REQUEST_WITH_SLEEP_MS" exit 1 fi @@ -128,273 +158,289 @@ if [[ $(echo "$REQUEST_WITH_INVALID_SLEEP_MS<2" | bc -l) == 1 ]]; then passed "Request with invalid response delay passed" else failed "Request with invalid response delay failed" - echo $REQUEST_WITH_INVALID_SLEEP_MS + echo "$REQUEST_WITH_INVALID_SLEEP_MS" exit 1 fi REQUEST=$(curl -s -X PUT -H "Arbitrary:Header" -d aaa=bbb http://localhost:8080/hello-world) -if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ - [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ - [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] -then +if [[ "$(echo "$REQUEST" | jq -r '.path')" == '/hello-world' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.method')" == 'PUT' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.headers.arbitrary')" == 'Header' ]]; then passed "HTTP request with arbitrary header passed." else failed "HTTP request with arbitrary header failed." - echo $REQUEST | jq + echo "$REQUEST" | jq exit 1 fi message " Make JSON request, and test that json is in the output. " REQUEST=$(curl -s -X POST -H "Content-Type: application/json" -d '{"a":"b"}' http://localhost:8080/) -if [ $(echo $REQUEST | jq -r '.json.a') == 'b' ] -then +if [[ "$(echo "$REQUEST" | jq -r '.json.a')" == 'b' ]]; then passed "JSON test passed." else failed "JSON test failed." - echo $REQUEST | jq + echo "$REQUEST" | jq exit 1 fi message " Make JSON request with gzip Content-Encoding, and test that json is in the output. " REQUEST=$(echo -n '{"a":"b"}' | gzip | curl -s -X POST -H "Content-Encoding: gzip" -H "Content-Type: application/json" --data-binary @- http://localhost:8080/) -if [ $(echo $REQUEST | jq -r '.json.a') == 'b' ] -then +if [[ "$(echo "$REQUEST" | jq -r '.json.a')" == 'b' ]]; then passed "JSON test passed." else failed "JSON test failed." - echo $REQUEST | jq + echo "$REQUEST" | jq exit 1 fi REQUEST=$(curl -s -X POST -H "Content-Type: application/json" -d 'not-json' http://localhost:8080) -if [ $(echo $REQUEST | jq -r '.json') == 'null' ]; then +if [[ "$(echo "$REQUEST" | jq -r '.json')" == 'null' ]]; then passed "JSON with Invalid Body test passed." else failed "JSON with Invalid Body test failed." - echo $REQUEST | jq + echo "$REQUEST" | jq exit 1 fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with max header size " docker run -d --rm -e MAX_HEADER_SIZE=1000 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 10 +wait_for_ready message " Make request with a header within limit." LARGE_HEADER_VALUE=$(head -c 600 &1) -if echo $REQUEST | grep -q "HTTP/1.1 431 Request Header Fields Too Large"; then +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 failed "Large header test failed." - echo $REQUEST + echo "$REQUEST" exit 1 fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +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 -sleep 5 +wait_for_ready message " Make http(s) request, and test the path, method and header. " REQUEST=$(curl -s -k -X PUT -H "Arbitrary:Header" -d aaa=bbb https://localhost:8443/hello-world) -if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ - [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ - [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] -then +if [[ "$(echo "$REQUEST" | jq -r '.path')" == '/hello-world' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.method')" == 'PUT' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.headers.arbitrary')" == 'Header' ]]; then passed "HTTPS request passed." else failed "HTTPS request failed." - echo $REQUEST | jq + echo "$REQUEST" | jq exit 1 fi REQUEST=$(curl -s -X PUT -H "Arbitrary:Header" -d aaa=bbb http://localhost:8080/hello-world) -if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ - [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ - [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] -then +if [[ "$(echo "$REQUEST" | jq -r '.path')" == '/hello-world' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.method')" == 'PUT' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.headers.arbitrary')" == 'Header' ]]; then passed "HTTP request passed." else failed "HTTP request failed." - echo $REQUEST | jq + echo "$REQUEST" | jq exit 1 fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with empty responses " docker run -d --rm -e ECHO_BACK_TO_CLIENT=false --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready + REQUEST=$(curl -s -k http://localhost:8080/a/b/c) -if [[ -z ${REQUEST} ]] +if [[ -z "$REQUEST" ]] then passed "Response is empty." else failed "Expected empty response, but got a non-empty response." - echo $REQUEST + echo "$REQUEST" exit 1 fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with response body only " docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready RESPONSE=$(curl -s -k -X POST -d 'cauliflower' http://localhost:8080/a/b/c?response_body_only=true) -if [[ ${RESPONSE} == "cauliflower" ]] -then +if [[ "$RESPONSE" == "cauliflower" ]]; then passed "Response body only received." else failed "Expected response body only." - echo $RESPONSE + echo "$RESPONSE" exit 1 fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with JWT_HEADER " docker run -d --rm -e JWT_HEADER=Authentication --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready REQUEST=$(curl -s -k -H "Authentication: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" https://localhost:8443/ ) -if [ $(echo $REQUEST | jq -r '.jwt.header.typ') == 'JWT' ] && \ - [ $(echo $REQUEST | jq -r '.jwt.header.alg') == 'HS256' ] && \ - [ $(echo $REQUEST | jq -r '.jwt.payload.sub') == '1234567890' ] -then +if [[ "$(echo "$REQUEST" | jq -r '.jwt.header.typ')" == 'JWT' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.jwt.header.alg')" == 'HS256' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.jwt.payload.sub')" == '1234567890' ]]; then passed "JWT request passed." else failed "JWT request failed." - echo $REQUEST | jq + echo "$REQUEST" | jq exit 1 fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with LOG_IGNORE_PATH (normal path)" docker run -d --rm -e LOG_IGNORE_PATH=/ping --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready "/ping" + curl -s -k -X POST -d "banana" https://localhost:8443/ping > /dev/null -if [ $(docker logs http-echo-tests | wc -l) == 2 ] && \ - ! [ $(docker logs http-echo-tests | grep banana) ] +# There should be 3 lines, the "listening on...", the /ping ready test, and the /ping POST test. +LOG_OUTPUT="$(docker logs http-echo-tests 2>&1)" +LINE_COUNT=$(( $(wc -l <<< "$LOG_OUTPUT") )) +if [[ "$LINE_COUNT" == 3 ]] && \ + ! grep -q banana <<< "$LOG_OUTPUT" then passed "LOG_IGNORE_PATH ignored the /ping path" else failed "LOG_IGNORE_PATH failed" - docker logs http-echo-tests + echo "$LOG_OUTPUT" exit 1 fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with LOG_IGNORE_PATH (regex path)" docker run -d --rm -e LOG_IGNORE_PATH="^\/ping|^\/health|^\/metrics" --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready "/health" + curl -s -k -X POST -d "banana" https://localhost:8443/metrics > /dev/null -if [ $(docker logs http-echo-tests | wc -l) == 2 ] && \ - ! [ $(docker logs http-echo-tests | grep banana) ] +# There should be 3 lines, the "listening on...", the /health ready test, and the /metrics POST test. +LOG_OUTPUT="$(docker logs http-echo-tests 2>&1)" +LINE_COUNT=$(( $(wc -l <<< "$LOG_OUTPUT") )) +if [[ "$LINE_COUNT" == 3 ]] && \ + ! grep -q banana <<< "$LOG_OUTPUT" then passed "LOG_IGNORE_PATH ignored the /metrics path" else failed "LOG_IGNORE_PATH failed" - docker logs http-echo-tests + echo "$LOG_OUTPUT" exit 1 fi # Test a positive case where the path is not ignored curl -s -k -X POST -d "strawberry" https://localhost:8443/veryvisible > /dev/null -if [[ $(docker logs http-echo-tests | grep strawberry) ]] +LOG_OUTPUT="$(docker logs http-echo-tests 2>&1)" +if grep -q strawberry <<< "$LOG_OUTPUT" then passed "LOG_IGNORE_PATH didn't ignore the /veryvisible path" else failed "LOG_IGNORE_PATH failed, it should not ignore the /veryvisible path" - docker logs http-echo-tests + echo "$LOG_OUTPUT" exit 1 fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with LOG_IGNORE_PATH (ignore all paths) " docker run -d --rm -e LOG_IGNORE_PATH=".*" --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready "/hello" + curl -s -k -X POST -d "banana" https://localhost:8443/ > /dev/null -if [ $(docker logs http-echo-tests | wc -l) == 2 ] && \ - ! [ $(docker logs http-echo-tests | grep banana) ] +# There should be 3 lines, the "listening on", the "/hello" ready test, and the POST banana test. +LOG_OUTPUT="$(docker logs http-echo-tests 2>&1)" +LINE_COUNT=$(( $(wc -l <<< "$LOG_OUTPUT") )) +if [[ "$LINE_COUNT" == 3 ]] && \ + ! grep -q banana <<< "$LOG_OUTPUT" then passed "LOG_IGNORE_PATH ignored all paths" else failed "LOG_IGNORE_PATH failed" - docker logs http-echo-tests + echo "$LOG_OUTPUT" exit 1 fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with DISABLE_REQUEST_LOGS " docker run -d --rm -e DISABLE_REQUEST_LOGS=true --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready "/healthy" + curl -s -k -X GET https://localhost:8443/strawberry > /dev/null -if [ $(docker logs http-echo-tests | grep -c "GET /strawberry HTTP/1.1") -eq 0 ] +LOG_OUTPUT="$(docker logs http-echo-tests 2>&1)" +if [[ "$(grep -c "GET /strawberry HTTP/1.1" <<< "$LOG_OUTPUT")" -eq 0 ]] then passed "DISABLE_REQUEST_LOGS disabled Express HTTP logging" else failed "DISABLE_REQUEST_LOGS failed" - docker logs http-echo-tests + echo "$LOG_OUTPUT" exit 1 fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with CORS_CONFIG" docker run -d --rm \ -e CORS_ALLOW_ORIGIN="http://example.com" -e CORS_ALLOW_HEADERS="x-custom-test-header" \ --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready + # Check if the expected CORS headers are present in the response if curl -s -i http://localhost:8080/ 2>&1 | grep -q -E \ "Access-Control-Allow-Headers: x-custom-test-header" && @@ -408,35 +454,37 @@ else fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with LOG_WITHOUT_NEWLINE " docker run -d --rm -e LOG_WITHOUT_NEWLINE=1 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready + curl -s -k -X POST -d "tiramisu" https://localhost:8443/ > /dev/null -if [ $(docker logs http-echo-tests | wc -l) == 3 ] && \ - [ $(docker logs http-echo-tests | grep tiramisu) ] +# There will be 4 lines, the Listening on, the / ready test and response, the POST test and response +LOG_OUTPUT="$(docker logs http-echo-tests 2>&1)" +LINE_COUNT=$(( $(wc -l <<< "$LOG_OUTPUT") )) +if [[ "$LINE_COUNT" == 5 ]] && \ + grep -q tiramisu <<< "$LOG_OUTPUT" then passed "LOG_WITHOUT_NEWLINE logged output in single line" else failed "LOG_WITHOUT_NEWLINE failed" - docker logs http-echo-tests + echo "$LOG_OUTPUT" exit 1 fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Check that container is running as a NON ROOT USER by default" docker run -d --name http-echo-tests --rm mendhak/http-https-echo:testing WHOAMI=$(docker exec http-echo-tests whoami) -if [ "$WHOAMI" == "node" ] +if [[ "$WHOAMI" == "node" ]] then passed "Running as non root user" else @@ -445,19 +493,19 @@ else fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Check that container is running as user different that the user defined in image" IMAGE_USER="$(docker image inspect mendhak/http-https-echo:testing -f '{{ .Config.User }}')" CONTAINER_USER="$((IMAGE_USER + 1000000))" docker run -d --name http-echo-tests --rm -u "${CONTAINER_USER}" -p 8080:8080 mendhak/http-https-echo:testing -sleep 5 +wait_for_ready + curl -s http://localhost:8080 > /dev/null WHOAMI="$(docker exec http-echo-tests id -u)" -if [ "$WHOAMI" == "$CONTAINER_USER" ] +if [[ "$WHOAMI" == "$CONTAINER_USER" ]] then passed "Running as $CONTAINER_USER user" else @@ -466,8 +514,7 @@ else fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Check that mTLS server responds with client certificate details" # Generate a new self signed cert locally @@ -475,10 +522,11 @@ openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout testpk.pem -subj "/CN=client.example.net" \ -addext "subjectAltName=DNS:client.example.net" docker run -d --rm -e MTLS_ENABLE=1 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready + COMMON_NAME="$(curl -sk --cert fullchain.pem --key testpk.pem https://localhost:8443/ | jq -r '.clientCertificate.subject.CN')" SAN="$(curl -sk --cert fullchain.pem --key testpk.pem https://localhost:8443/ | jq -r '.clientCertificate.subjectaltname')" -if [ "$COMMON_NAME" == "client.example.net" ] && [ "$SAN" == "DNS:client.example.net" ] +if [[ "$COMMON_NAME" == "client.example.net" && "$SAN" == "DNS:client.example.net" ]] then passed "Client certificate details are present in the output" else @@ -488,7 +536,7 @@ fi message " Check if certificate is not passed, then client certificate details are empty" CLIENT_CERT="$(curl -sk https://localhost:8443/ | jq -r '.clientCertificate')" -if [ "$CLIENT_CERT" == "{}" ] +if [[ "$CLIENT_CERT" == "{}" ]] then passed "Client certificate details are not present in the response" else @@ -496,9 +544,9 @@ else exit 1 fi -message " Check that HTTP server does not have any client certificate property" -CLIENT_CERT=$(curl -sk --cert cert.pem --key testpk.pem http://localhost:8080/ | jq 'has("clientCertificate")') -if [ "$CLIENT_CERT" == "false" ] +message " Check that plaintext HTTP port does not have any client certificate property" +CLIENT_CERT=$(curl -s http://localhost:8080/ | jq 'has("clientCertificate")') +if [[ "$CLIENT_CERT" == "false" ]] then passed "Client certificate details are not present in regular HTTP server" else @@ -507,8 +555,7 @@ else fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Check that SSL certificate and private key are loaded from custom location" cert_common_name="server.example.net" @@ -527,12 +574,12 @@ docker run -d --rm \ -e HTTPS_CERT_FILE="${container_https_cert_file}" \ -v "${https_key_file}:${container_https_key_file}:ro,z" \ -e HTTPS_KEY_FILE="${container_https_key_file}" \ - --name http-echo-tests -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 + --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready REQUEST_WITH_STATUS_CODE="$(curl -s --cacert "$(pwd)/server_fullchain.pem" -o /dev/null -w "%{http_code}" \ --resolve "${cert_common_name}:8443:127.0.0.1" "https://${cert_common_name}:8443/hello-world")" -if [ "${REQUEST_WITH_STATUS_CODE}" = 200 ] +if [[ "${REQUEST_WITH_STATUS_CODE}" == 200 ]] then passed "Server certificate and private key are loaded from configured custom location" else @@ -541,15 +588,15 @@ else fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Check that environment variables returned in response if enabled" 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:testing -sleep 5 +wait_for_ready + RESPONSE_BODY="$(curl -sk https://localhost:8443/ | jq -r '.env.ECHO_INCLUDE_ENV_VARS')" -if [ "$RESPONSE_BODY" == "1" ] +if [[ "$RESPONSE_BODY" == "1" ]] then passed "Environment variables present in the output" else @@ -558,15 +605,15 @@ else fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Check that environment variables are not present in response by default" docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready + RESPONSE_BODY_ENV_CHECK="$(curl -sk https://localhost:8443/ | jq 'has("env")')" -if [ "$RESPONSE_BODY_ENV_CHECK" == "false" ] +if [[ "$RESPONSE_BODY_ENV_CHECK" == "false" ]] then passed "Environment variables not present in the output by default" else @@ -575,12 +622,12 @@ else fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with PROMETHEUS disabled " docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready + curl -s -k -X POST -d "tiramisu" https://localhost:8443/ > /dev/null # grep for http_request_duration_seconds_count ensure it is not present at /metric path @@ -596,12 +643,12 @@ else fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with PROMETHEUS enabled " docker run -d -e PROMETHEUS_ENABLED=true --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready + curl -s -k -X POST -d "tiramisu" https://localhost:8443/ > /dev/null METRICS_CHECK="$(curl -sk http://localhost:8080/metrics | grep http_request_duration_seconds_count )" @@ -616,13 +663,12 @@ fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with PRESERVE_HEADER_CASE enabled " docker run -d -e PRESERVE_HEADER_CASE=true --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready -sleep 5 HEADER_CASE_CHECK=$(curl -s -H "prEseRVe-CaSE: A1b2C3" -H 'x-a-b: 999' -H 'X-a-B: 13' localhost:8080 | jq -r '.headers."prEseRVe-CaSE"') if [[ "$HEADER_CASE_CHECK" == "A1b2C3" ]] then @@ -633,13 +679,13 @@ else fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with a custom response body from a file " echo "

Hello World

" > test.html -docker run -d --rm -v ${PWD}/test.html:/app/test.html --name http-echo-tests -p 8080:8080 -e OVERRIDE_RESPONSE_BODY_FILE_PATH=/test.html -t mendhak/http-https-echo:testing -sleep 5 +docker run -d --rm -v "${PWD}/test.html:/app/test.html" --name http-echo-tests -p 8080:8080 -e OVERRIDE_RESPONSE_BODY_FILE_PATH=/test.html -t mendhak/http-https-echo:testing +wait_for_ready + RESPONSE_BODY=$(curl -s http://localhost:8080) if [[ "$RESPONSE_BODY" == "

Hello World

" ]] then @@ -650,14 +696,13 @@ else fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Start container with signed cookies support " # Set cookie secret for signing/verifying cookies docker run -d --rm -e COOKIE_SECRET=mysecretkey123 \ --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready SIGNED_COOKIE=$(node -e "var crypto = require('crypto'); @@ -673,40 +718,34 @@ console.log(sign('my-value','mysecretkey123'));") RESPONSE=$(curl -s http://localhost:8080/ -H "Cookie: mysigned=s:${SIGNED_COOKIE}") -if [ $(echo $RESPONSE | jq -r '.signedCookies.mysigned') == 'my-value' ] -then +if [[ "$(echo "$RESPONSE" | jq -r '.signedCookies.mysigned')" == 'my-value' ]]; then passed "Signed cookie test passed." else failed "Signed cookie test failed." - echo $RESPONSE | jq + echo "$RESPONSE" | jq exit 1 fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove message " Check that regular cookies are returned in response " docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing -sleep 5 +wait_for_ready RESPONSE=$(curl -s http://localhost:8080/ -H "Cookie: foo=bar; baz=qux") -if [ $(echo $RESPONSE | jq -r '.cookies.foo') == 'bar' ] && \ - [ $(echo $RESPONSE | jq -r '.cookies.baz') == 'qux' ] -then +if [[ "$(echo "$RESPONSE" | jq -r '.cookies.foo')" == 'bar' ]] && \ + [[ "$(echo "$RESPONSE" | jq -r '.cookies.baz')" == 'qux' ]]; then passed "Cookies returned in response test passed." else failed "Cookies returned in response test failed." - echo $RESPONSE | jq + echo "$RESPONSE" | jq exit 1 fi message " Stop containers " -docker stop http-echo-tests -sleep 5 +stop_and_remove -popd -rm -rf testarea message "DONE"