From 30f9ebec78da3f3e511200d88dd7192ed5afdf64 Mon Sep 17 00:00:00 2001 From: Matthew Baggett Date: Thu, 25 Jan 2024 11:41:42 +0100 Subject: [PATCH] Feature: BOUNCER_HOST_OVERRIDE --- bouncer/Dockerfile | 5 ++++- bouncer/Makefile | 1 + bouncer/NginxTemplate.twig | 6 +++++- bouncer/Readme.md | 21 +++++++++++---------- bouncer/bouncer | 34 ++++++++++++++++++++++++++++++++-- 5 files changed, 53 insertions(+), 14 deletions(-) diff --git a/bouncer/Dockerfile b/bouncer/Dockerfile index dbb6cdc..7c7b6f6 100644 --- a/bouncer/Dockerfile +++ b/bouncer/Dockerfile @@ -1,6 +1,9 @@ FROM benzine/php:cli-8.1 as bouncer +ARG BUILD_DATE ARG GIT_SHA -ENV GIT_SHA=${GIT_SHA} +ENV BUILD_DATE=${BUILD_DATE} \ + GIT_SHA=${GIT_SHA} + LABEL maintainer="Matthew Baggett " \ org.label-schema.vcs-url="https://github.com/benzine-framework/docker" \ org.opencontainers.image.source="https://github.com/benzine-framework/docker" diff --git a/bouncer/Makefile b/bouncer/Makefile index eb93730..e05d7a6 100644 --- a/bouncer/Makefile +++ b/bouncer/Makefile @@ -6,6 +6,7 @@ fix: php-cs-fixer build-n-push: docker build \ + --build-arg BUILD_DATE=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ") \ --build-arg GIT_SHA=$(shell git rev-parse HEAD) \ --tag benzine/bouncer \ --tag ghcr.io/benzine-framework/bouncer \ diff --git a/bouncer/NginxTemplate.twig b/bouncer/NginxTemplate.twig index 2e24088..cf287eb 100644 --- a/bouncer/NginxTemplate.twig +++ b/bouncer/NginxTemplate.twig @@ -30,7 +30,11 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto https; +{% if hasHostOverride %} + proxy_set_header Host {{ hostOverride }}; +{% else %} proxy_set_header Host $host; +{% endif %} {% if hasAuth %} auth_basic "closed site"; @@ -51,7 +55,7 @@ server { {% endif %} proxy_pass {{ targetPath }}; - } + %} } {% if not allowNonSSL %} diff --git a/bouncer/Readme.md b/bouncer/Readme.md index 9b722c5..768c04d 100644 --- a/bouncer/Readme.md +++ b/bouncer/Readme.md @@ -34,16 +34,17 @@ These should not be confused. ### Served Instance Configuration These environment variables need to be applied to the CONSUMING SERVICE and not the loadbalancer container itself. -| Key | Example | Behaviour | -|--------------------------------|-------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| -| BOUNCER_DOMAIN | "a.example.com" | The domain that should be directed to this container | -| BOUNCER_AUTH | "username:password" e.g "root:toor" | Add a HTTP BASIC auth requirement to this hostname. | -| BOUNCER_LETSENCRYPT | Values are "yes" or "true", anything else is false | To enable, or disable Lets Encrypt service for this hostname | -| BOUNCER_TARGET_PORT | 9000 | Explicitly define the port you want to hit the service on, in case of ambiguity | -| BOUNCER_ALLOW_NON_SSL | Defaults to enabled. Values are "yes" or "true", anything else is false | Should HTTP only traffic be allowed to hit this service? If disabled, http traffic is forwarded towards https | -| BOUNCER_ALLOW_WEBSOCKETS | Defaults to enabled. Values are "yes" or "true", anything else is false | Enable websocket behaviour | -| BOUNCER_ALLOW_LARGE_PAYLOADS | Defaults to disabled. | Allows overriding the default nginx payload size. Related to BOUNCER_MAX_PAYLOADS_MEGABYTES | -| BOUNCER_MAX_PAYLOADS_MEGABYTES | numbers | Size of max payload to allow, in megabytes. Requires BOUNCER_ALLOW_LARGE_PAYLOADS to be enabled | +| Key | Example | Behaviour | +|--------------------------------|-------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| BOUNCER_DOMAIN | "a.example.com" | The domain that should be directed to this container | +| BOUNCER_AUTH | "username:password" e.g "root:toor" | Add a HTTP BASIC auth requirement to this hostname. | +| BOUNCER_HOST_OVERRIDE | "localhost:80" | Override the host header that is sent to the service. Useful for services that are not aware of their own hostname, or annoying things like [mitmproxy](https://github.com/mitmproxy/mitmproxy/issues/3234) | +| BOUNCER_LETSENCRYPT | Values are "yes" or "true", anything else is false | To enable, or disable Lets Encrypt service for this hostname | +| BOUNCER_TARGET_PORT | 9000 | Explicitly define the port you want to hit the service on, in case of ambiguity | +| BOUNCER_ALLOW_NON_SSL | Defaults to enabled. Values are "yes" or "true", anything else is false | Should HTTP only traffic be allowed to hit this service? If disabled, http traffic is forwarded towards https | +| BOUNCER_ALLOW_WEBSOCKETS | Defaults to enabled. Values are "yes" or "true", anything else is false | Enable websocket behaviour | +| BOUNCER_ALLOW_LARGE_PAYLOADS | Defaults to disabled. | Allows overriding the default nginx payload size. Related to BOUNCER_MAX_PAYLOADS_MEGABYTES | +| BOUNCER_MAX_PAYLOADS_MEGABYTES | numbers | Size of max payload to allow, in megabytes. Requires BOUNCER_ALLOW_LARGE_PAYLOADS to be enabled | ## Security considerations If you're putting this behind access control to the docker socket, it will need access to the /swarm /services and /containers endpoints of the docker api. diff --git a/bouncer/bouncer b/bouncer/bouncer index b78d8e3..70fba33 100755 --- a/bouncer/bouncer +++ b/bouncer/bouncer @@ -6,6 +6,7 @@ use AdamBrett\ShellWrapper\Command\Builder as CommandBuilder; use AdamBrett\ShellWrapper\Runners\Exec; use Aws\S3\S3Client; use Bramus\Monolog\Formatter\ColoredLineFormatter; +use Carbon\Carbon; use GuzzleHttp\Client as Guzzle; use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\ServerException; @@ -39,6 +40,8 @@ class BouncerTarget private ?string $username = null; private ?string $password = null; + private ?string $hostOverride = null; + public function __construct( private Logger $logger ) { @@ -60,9 +63,28 @@ class BouncerTarget 'proxyTimeoutSeconds' => $this->getProxyTimeoutSeconds(), 'hasAuth' => $this->hasAuth(), 'authFile' => $this->getAuthFileName(), + 'hasHostOverride' => $this->hasHostOverride(), + 'hostOverride' => $this->getHostOverride(), ]; } + public function getHostOverride(): ?string + { + return $this->hostOverride; + } + + public function hasHostOverride(): bool + { + return $this->hostOverride !== null; + } + + public function setHostOverride(string $hostOverride): BouncerTarget + { + $this->hostOverride = $hostOverride; + + return $this; + } + public function getUsername(): ?string { return $this->username; @@ -592,12 +614,15 @@ class Bouncer public function run(): void { $gitHash = substr($this->environment['GIT_SHA'], 0, 7); - $this->logger->info(sprintf('%s Starting Bouncer git=%s...', Emoji::CHARACTER_TIMER_CLOCK, $gitHash)); + $this->logger->info('{emoji} Starting Bouncer git={git_sha}...', ['emoji' => Emoji::CHARACTER_TIMER_CLOCK, 'git_sha' => $gitHash]); + + $buildDate = Carbon::parse($this->environment['BUILD_DATE']); + $this->logger->info('{emoji} Built on {build_date}, {build_ago}', ['emoji' => Emoji::CHARACTER_TIMER_CLOCK, 'build_date' => $buildDate->toDateTimeString(), 'build_ago' => $buildDate->ago()]); try { $this->stateHasChanged(); } catch (ConnectException $connectException) { - $this->logger->critical(sprintf('%s Could not connect to docker socket! Did you map it?', Emoji::CHARACTER_CRYING_CAT)); + $this->logger->critical('{emoji} Could not connect to docker socket! Did you map it?', ['emoji' => Emoji::CHARACTER_CRYING_CAT]); exit; } @@ -625,6 +650,11 @@ class Bouncer break; + case 'BOUNCER_HOST_OVERRIDE': + $bouncerTarget->setHostOverride($eVal); + + break; + case 'BOUNCER_LETSENCRYPT': $bouncerTarget->setLetsEncrypt(in_array(strtolower($eVal), ['yes', 'true'], true));