chore: update configuration and Docker setup for JWT and test results
ci/woodpecker/push/build-arm Pipeline was successful

Enhanced the .gitignore to exclude test results and updated the Dockerfile to include a new entrypoint script for improved container initialization. Refactored JWT configuration to support additional parameters for automatic refresh intervals, ensuring better control over token management. Updated the ConfigurationResolver to enforce required environment variables without hardcoded fallbacks, enhancing security and flexibility.
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-15 03:23:23 +03:00
parent 7025f4d075
commit 78dea8ebab
40 changed files with 1990 additions and 510 deletions
@@ -1,12 +1,12 @@
# 07 — Host (Composition Root)
**Spec source**: `../../../suite/_docs/00_top_level_architecture.md` § Edge compose excerpt -- confirms the env vars (`DATABASE_URL`, `JWT_SECRET`), port (`5002:8080`), and DB target (`postgres-local`).
**Spec source**: `../../../suite/_docs/00_top_level_architecture.md` § Edge compose excerpt confirms the port (`5002:8080`) and DB target (`postgres-local`). **The env-var contract in suite docs still references the legacy `JWT_SECRET`** and predates this service's transition to JWKS-based JWT validation; the four-variable env contract documented below is the verified current state in code, and the suite docs are flagged for sync in `_docs/02_document/05_drift_findings_2026-05-14.md`.
**Implementation status**: ✅ implemented.
> **NOTE (forward-looking)**: post-rename. Today's source has `Azaion.Flights` namespace + `dotnet Azaion.Flights.dll` entrypoint + container image `azaion/flights:*-arm`. Renames + DLL/image/compose changes tracked under Jira AZ-EPIC children B5 (namespace), B10 (Dockerfile + Woodpecker + suite compose).
**Files**: `Program.cs`, `GlobalUsings.cs`
**Files**: `Program.cs`, `GlobalUsings.cs`, `Infrastructure/ConfigurationResolver.cs`, `Infrastructure/CorsConfigurationValidator.cs`
## 1. High-Level Overview
@@ -50,9 +50,26 @@ None. The host has no exported types -- its surface is the running HTTP server.
**Error Handling**: Delegated to `06_http_conventions`' middleware, placed FIRST in the pipeline so it wraps everything else.
**Configuration**: Reads `DATABASE_URL` and `JWT_SECRET` from `IConfiguration` -> `Environment.GetEnvironmentVariable` -> hardcoded dev fallback. Both fallbacks are dev-only and MUST be overridden in production.
**Configuration**: All required values flow through `Infrastructure/ConfigurationResolver.cs``ResolveRequiredOrThrow`. Resolution order per value: `Environment.GetEnvironmentVariable(envVar)``IConfiguration[configKey]` → throws `InvalidOperationException` at startup with a message naming both the env var and the config key. There are **no hardcoded dev fallbacks**; a misconfigured production deploy cannot silently boot.
**`ConvertPostgresUrl` helper**: ad-hoc parser converting `postgresql://user[:pass]@host[:port]/db` to Npgsql key=value form. Does not URL-decode user/password -- caveat for credentials with `@`, `:`, `/`, `%`.
| Env var | Config key | Required? | Purpose |
|---------|------------|-----------|---------|
| `DATABASE_URL` | `Database:Url` | **Yes** | Either Npgsql key=value form OR a `postgresql://` URI (converted via `ConvertPostgresUrl`) |
| `JWT_ISSUER` | `Jwt:Issuer` | **Yes** | Expected `iss` claim value (see `05_identity`) |
| `JWT_AUDIENCE` | `Jwt:Audience` | **Yes** | Expected `aud` claim value (see `05_identity`) |
| `JWT_JWKS_URL` | `Jwt:JwksUrl` | **Yes** | HTTPS URL of admin's JWKS endpoint (see `05_identity`) |
| `CorsConfig:AllowedOrigins` | (same) | No (defaults to `[]`) | String array of allowed origins for the CORS policy |
| `CorsConfig:AllowAnyOrigin` | (same) | No (defaults to `false`) | When `true`, applies `AllowAnyOrigin/Method/Header` regardless of origins |
The legacy `JWT_SECRET` env var is **no longer consulted**; `05_identity` documents the JWKS-based replacement.
**CORS gating** (`Infrastructure/CorsConfigurationValidator.cs`):
- `EnsureSafeForEnvironment(origins, allowAnyOrigin, environmentName)` THROWS `InvalidOperationException` in `Production` (case-insensitive match on the `ASPNETCORE_ENVIRONMENT` value) when origins are empty AND `allowAnyOrigin` is `false`. The host refuses to start with an implicit permissive policy in Production.
- `ShouldUsePermissivePolicy(origins, allowAnyOrigin)` returns `true` when `allowAnyOrigin == true` OR origins is empty — used by the CORS policy builder. In non-Production environments with empty origins this falls back to permissive.
- `ShouldWarnAboutPermissiveDefault(origins, allowAnyOrigin)` is `true` when origins are empty AND `allowAnyOrigin` is `false` (implicit permissive). When true, the host logs `PermissiveDefaultWarning` at startup with the current environment name.
**`ConvertPostgresUrl` helper**: ad-hoc parser converting `postgresql://user[:pass]@host[:port]/db` to Npgsql key=value form. Does not URL-decode user/password — caveat for credentials with `@`, `:`, `/`, `%`.
## 6. Extensions and Helpers
@@ -60,10 +77,11 @@ None. The host has no exported types -- its surface is the running HTTP server.
## 7. Caveats & Edge Cases
- **No environment guards**: Swagger and the dev fallbacks for secrets are NOT gated on `IsDevelopment()`. If `JWT_SECRET` is unset in production, the service silently runs with the well-known development secret.
- **CORS open by default**: `AllowAnyOrigin/Method/Header` applied unconditionally. Spec doesn't mandate a CORS policy -- likely safe behind suite's reverse proxy on edge, but worth confirming.
- **Swagger is unconditional**: Swagger UI + JSON spec are mounted regardless of environment (no `IsDevelopment()` guard). This is the **only remaining** aspect of ADR-005 that still applies — the legacy "dev-fallback secret" aspect of ADR-005 is now obsolete (`ConfigurationResolver.ResolveRequiredOrThrow` throws on any missing value at startup).
- **CORS hard-fail is `Production`-only**. In `Staging` or any custom environment name that is not literal `Production` (case-insensitive), an empty allow-list with `AllowAnyOrigin=false` falls back to permissive (with a startup warning) instead of throwing. Operators deploying to a "Staging" tier that should be locked down need to set `CorsConfig:AllowedOrigins` explicitly — the validator will not enforce it for them.
- **JWKS startup dependency**: the first protected request after process start triggers a synchronous HTTPS fetch to `JWT_JWKS_URL`. If `admin` is unreachable at that moment, the request fails 500 from `05_identity`'s `IssuerSigningKeyResolver`. Once cached, request-path validation does not call `admin`.
- **Migrator failure crashes the process** at startup. Container orchestrator (Watchtower-restarted Docker) is expected to bring it back; `flight-gate` (per `../../../suite/_docs/00_top_level_architecture.md`) ensures this doesn't happen mid-mission.
- **No HTTPS redirection** middleware; assumes a TLS-terminating reverse proxy upstream (Caddy fronting Gitea is documented but in-deployment TLS termination is environment-specific).
- **No HTTPS redirection** middleware; assumes a TLS-terminating reverse proxy upstream (Caddy fronting Gitea is documented but in-deployment TLS termination is environment-specific). Note: `JWT_JWKS_URL` is independently constrained to HTTPS by `HttpDocumentRetriever { RequireHttps = true }` inside `05_identity`.
- **Port 8080** matches the Dockerfile `EXPOSE 8080` and edge compose `5002:8080` mapping per `../../../suite/_docs/00_top_level_architecture.md` excerpt.
- **No GPS-Denied service registration** here. Earlier drafts of this doc reserved a slot for a GPS-Denied feature component; per Jira AZ-EPIC child B7, GPS-Denied lives in a separate (out-of-this-repo) service, so this host registers only `VehicleService`, `MissionService`, `WaypointService`.