Files
missions/_docs/00_problem/security_approach.md
T
Oleksandr Bezdieniezhnykh 7025f4d075 refactor: enhance JWT authentication and CORS configuration
Updated JWT authentication to use configuration values instead of hardcoded secrets, improving security and flexibility. Enhanced CORS policy to conditionally allow origins based on configuration settings, with logging for permissive defaults. Updated README to reflect project renaming and clarify service context.
2026-05-14 19:48:25 +03:00

10 KiB

Security Approach — Azaion.Missions

Status: derived-from-code (autodev /document Step 6, 2026-05-14). All claims below trace to actual code, configuration, or a tracked suite-level finding. Items called out as "currently divergent" are intentional carry-forward — see _docs/02_document/architecture.md § 8 ADRs and 00_discovery.md § Spec ↔ Code Divergences.


1. Authentication

Mechanism: JWT bearer (HS256) with local validation only — this service never calls back to the issuing admin service.

Trust model: a single shared HMAC secret (JWT_SECRET) is provisioned to admin (issuer) and to every backend service on each edge device (validators). Rotation requires a coordinated re-deploy across all of them.

Validation parameters (Auth/JwtExtensions.cs):

Parameter Value Notes
Algorithm HS256 (SymmetricSecurityKey(UTF-8(JWT_SECRET))) Symmetric → asymmetric switch is suite-wide concern, not in this Epic
ValidateLifetime true Tokens with exp in the past are rejected
ClockSkew TimeSpan.FromMinutes(1) Tighter than .NET's 5-min default
ValidateIssuer false Known CMMC L2 finding (suite-tracked AZ-487/AZ-494); consistent with shared-secret trust
ValidateAudience false Same finding as above
ValidateIssuerSigningKey true Always required when ValidateLifetime/ValidateIssuer are set explicitly

Failure outcomes:

Condition HTTP code
Missing Authorization header 401
Invalid signature 401
Expired token (with 1-min skew) 401
Token signed with old JWT_SECRET after rotation 401 (until coordinated re-deploy + re-login)
Valid signature + lifetime, but missing permissions=FL claim 403

admin outage: tokens issued before the outage continue to validate locally. This service does not require admin to be reachable for any flow. Once issued tokens expire, new logins fail at admin's end (UI concern), but this service stays up.

2. Authorization

Single named policy: "FL". Every controller action in 01_vehicle_catalog and 02_mission_planning carries [Authorize(Policy = "FL")]. The policy is satisfied by a permissions claim with value "FL".

Role → permission matrix is suite-level (../../suite/_docs/00_roles_permissions.md); this service does NOT enforce roles, only the FL permission.

No per-method authz: every protected endpoint has the same gate. There is no notion of "read-only operator" vs "full-access operator" inside this service.

Hardcoded policy name carries legacy wording: the string "FL" (originally "Flight") survives the rename to missions. Renaming the permission code is a fleet-wide auth change (would invalidate every issued token until new ones are minted) and is NOT in this Epic. Tracked as a TODO in ../../suite/_docs/00_roles_permissions.md.

Typo risk: the "FL" string is repeated in feature controllers as a raw string. A typo silently turns into a permanent 403 with no compile-time detection. Mitigation: code review + the module-layout.md § Verification Needed #4 entry.

No per-user attribution / audit: the JWT's sub / user-id claim is parsed by JwtBearerHandler into ClaimsPrincipal, but nothing in this service consumes it. Logs are timestamp-only — incident reconstruction requires correlation by request time, not by user.

3. Data protection

At rest: PostgreSQL on-disk encryption is the device-level concern (suite-level, NOT this service). This service does NOT encrypt data at the column level.

In transit:

  • The container EXPOSE 8080 is plain HTTP. TLS termination is handled by the suite's edge reverse proxy (per ../../suite/_docs/00_top_level_architecture.md).
  • No app.UseHttpsRedirection() in this service. If the reverse proxy is misconfigured or absent, traffic between the operator UI and this service may be cleartext on the local edge network.

Secrets management:

  • JWT_SECRET and DATABASE_URL are env vars (with hardcoded dev fallbacks). See § 6 below.
  • No secret manager (Vault, AWS SM, K8s Secrets) — secrets are baked into the device's docker compose env at provisioning time.
  • No runtime gate prevents startup with the dev fallback in production (ADR-005 carry-forward).

4. Input validation

None at the application layer. No [Required], no [Range], no min-length attributes; no custom validators. The following are all accepted by ASP.NET Core model binding without rejection:

Bad input Accepted today
CreateVehicleRequest.Name = "" yes
CreateVehicleRequest.BatteryCapacity = -1 yes
CreateVehicleRequest.Type = (VehicleType)999 yes — int casts to enum without range check
CreateWaypointRequest.OrderNum = -1 yes
CreateWaypointRequest.GeoPoint = null (or all three of Lat/Lon/Mgrs null) yes
GetMissionsQuery.Page = -1 / PageSize = 1_000_000 yes — no bounds

This is a carry-forward concern — input-shape testing is not a security gate today; the threat surface is mitigated by the closed edge network and authenticated single-operator workflow. Tightening is on the autodev backlog (Phase B feature cycle).

5. CORS

Open in every environment: AllowAnyOrigin / AllowAnyMethod / AllowAnyHeader in Program.cs. Spec does not mandate a CORS policy.

The closed edge network behind the suite reverse proxy is the deployment-shape mitigation. Worth confirming on the first production rollout that the upstream proxy whitelists origins; if not, this is a finding to surface.

6. Production-deploy footguns

These are explicit security-relevant risks the code carries today, all tracked at the suite level or as carry-forward:

Footgun Where Mitigation
Dev fallback for JWT_SECRET silently accepted in production if env var unset Program.cs (no IsDevelopment gate, ADR-005) Suite-level remediation pending; recommend "fail-fast at startup if JWT_SECRET is unset OR equals the well-known dev fallback"
Dev fallback for DATABASE_URL silently accepted in production if env var unset Program.cs Same pattern as JWT_SECRET; misconfigured deploy hits localhost Postgres on the device, which usually doesn't exist → process exits, but the failure mode is loud (crash) not silent
Swagger UI mounted unconditionally Program.cs (no IsDevelopment gate, ADR-005) Reverse-proxy-level allowlist on /swagger is the suite-level mitigation; verify on first production rollout
CORS AllowAnyOrigin/Method/Header in production Program.cs Reverse-proxy origin whitelist is the suite-level mitigation
No HTTPS redirection Program.cs (no app.UseHttpsRedirection()) Reverse proxy enforces TLS upstream
Stack trace logged for unhandled 500s Middleware/ErrorHandlingMiddleware.cs LogError(ex, ...) Stack is logged only — NOT returned in the HTTP response body (the 500 body is the generic "Internal server error" message from middleware)
JWT iss/aud validation disabled Auth/JwtExtensions.cs CMMC L2 row 3 finding; tracked at suite level under AZ-487 / AZ-494
Cascade-delete is NOT transaction-wrapped (data-integrity, not auth) Services/MissionService.cs, Services/WaypointService.cs (ADR-006) One-line fix queued; recommended to land with B6
Hardcoded permission string "FL" in feature controllers Controllers/{Vehicles,Missions}Controller.cs Risk: typo silently turns into permanent 403; mitigation by code review + module-layout.md
Permission code "FL" retains legacy "Flight" wording post-rename Auth/JwtExtensions.cs Fleet-wide auth change deferred (not in this Epic); TODO in ../../suite/_docs/00_roles_permissions.md

7. Audit logging

None at the application level today. The only structured log emitted by application code is _logger.LogError(ex, "Unhandled exception") in ErrorHandlingMiddleware for 500s. There is:

  • No correlation ID per request
  • No per-user attribution (the JWT user-id claim is not consumed)
  • No security-event log (auth failures are logged by JwtBearerHandler at default ASP.NET Core levels — typically Information, not surfaced as a dedicated audit channel)
  • No data-access audit (writes/deletes go directly through linq2db with no wrapper that emits an audit row)

Production incident response on this service today requires grep-by-timestamp correlation against the operator UI's logs and admin's issuance logs.

8. Threat model summary (one-paragraph)

The deployment shape — closed edge network, single operator per device, suite reverse proxy enforcing TLS and origin allowlisting upstream, Watchtower restart on crash — is the primary defence-in-depth layer for everything not handled by HS256 JWT validation and the FL permission gate. The known weak points (dev fallbacks not gated on IsDevelopment(), no input validation, no application-level audit log, iss/aud not validated) are documented and tracked, with the most critical ones (CMMC L2 row 3, default-vehicle race) under suite-level or B-ticket Jira IDs. This Epic (rename + GPS-Denied removal) does not change the security posture; it preserves every current invariant.

9. References

Concern File
Auth registration Auth/JwtExtensions.cs
Authorization attribute usage Controllers/AircraftsController.cs (post-B6: VehiclesController.cs), Controllers/FlightsController.cs (post-B6: MissionsController.cs)
Error envelope (no stack-leak) Middleware/ErrorHandlingMiddleware.cs
Env var resolution + dev fallbacks Program.cs
CMMC L2 scorecard ../../suite/_docs/05_security/cmmc_l2_scorecard.md
Roles & FL permission origin ../../suite/_docs/00_roles_permissions.md
ADR-005 (Swagger + dev fallbacks) _docs/02_document/architecture.md § 8
ADR-002 (PascalCase wire shape) _docs/02_document/architecture.md § 8
Component identity description _docs/02_document/components/05_identity/description.md
Component http-conventions description _docs/02_document/components/06_http_conventions/description.md