From aa8ca138655dcb8629c1d38294f559bbfb447af3 Mon Sep 17 00:00:00 2001 From: aarroyo Date: Fri, 21 Aug 2026 22:27:39 -0500 Subject: [PATCH] feat(infra): put a door in front of UAT so the Tracker can run the only way it runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Tracker was set to Staging here on the reasoning that DevBypass is for laptops. Correct in principle, unusable in fact: `AuthenticationExtensions.cs:66` refuses to register DevBypass outside Development and demands a UMS authority instead, and no identity provider is deployed. Measured on the live environment: `/api/v1/health` 200, `/api/v1/initiatives` 401 — every screen an empty error card while the health check stayed green. So the choice the Tracker offers today is Development or nothing, and Development authenticates every request as a full-permission administrator. Rather than ship that to a host anyone can reach, the guard moves to the perimeter: tracker-web's router carries an HTTP basic-auth middleware, so a request without the credential is refused by the proxy and never reaches a service that would trust it. The two halves are one mechanism. Development alone is an open admin console on the internet; the middleware alone protects a service that would have rejected the request anyway. Removing either one silently removes the protection, which is why the compose comment says so at both sites. The middleware is referenced as `uat-basicauth@file` and DEFINED in the proxy's dynamic configuration, deliberately not here. A bcrypt hash committed to a public repository is a hash published to everyone who can read it, and this repository is public. Reverts to Staging when Tracker ADR T-053 (consume UMS identity) lands. Co-Authored-By: Claude Opus 5 --- product/infra/docker-compose.uat.yml | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/product/infra/docker-compose.uat.yml b/product/infra/docker-compose.uat.yml index 72f7d7ba..2a69087d 100644 --- a/product/infra/docker-compose.uat.yml +++ b/product/infra/docker-compose.uat.yml @@ -196,9 +196,21 @@ services: restart: unless-stopped mem_limit: 768m environment: - # NOT Development: that enables the DevBypass auth scheme, which is right - # for a laptop and wrong for a host a client can reach. - ASPNETCORE_ENVIRONMENT: Staging + # Development, and not out of laziness — it is what the Tracker leaves + # available. `AuthenticationExtensions.cs:66` REFUSES to register the DevBypass + # scheme outside Development and demands a UMS authority instead; with no + # identity provider deployed, Staging means every data endpoint answers 401 + # while /health stays green — an environment that looks up and cannot be used. + # Measured in UAT: /api/v1/health 200, /api/v1/initiatives 401. + # + # So the choice the Tracker actually offers here is Development or nothing, and + # Development authenticates EVERY request as a full-permission administrator. + # That is defensible ONLY because the protection moved to the perimeter: the + # tracker-web router carries an HTTP basic-auth middleware, so an + # unauthenticated request never reaches this service at all. Remove one and the + # other stops being sufficient. When UMS lands (Tracker ADR T-053) this returns + # to Staging and the middleware can go. + ASPNETCORE_ENVIRONMENT: Development ASPNETCORE_URLS: http://+:8080 ConnectionStrings__DefaultConnection: Host=tracker-postgres;Port=5432;Database=evolith_tracker;Username=evolith;Password=${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD} CoreApi__BaseUrl: http://core-api:3000/api/v1 @@ -259,6 +271,14 @@ services: # The surface a UAT client actually opens in a browser. TRACKER_WEB, not # TRACKERWEB — see the note on core-api above. SERVICE_FQDN_TRACKER_WEB_8080: "" + # The perimeter door. This is what makes running the Tracker in Development + # defensible on a host reachable from the internet: the credential is checked by + # the proxy, so a request without it never reaches a service that would treat it + # as an administrator. The middleware itself — and therefore the password hash — + # lives in the proxy's dynamic configuration, NOT here: a bcrypt hash in a + # versioned file is a hash handed to everyone who can read the repository. + labels: + - "coolify.traefik.middlewares=uat-basicauth@file" expose: ["8080"] depends_on: tracker-api: