Files
missions/_docs/02_document/deployment/environment_strategy.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

6.6 KiB

Environment Strategy

NOTE (forward-looking): image tag, container name, and namespace reflect the post-rename state. Today's edge compose still references azaion/flights:${BRANCH}-arm and the container name is typically flights. Rename tracked under B10 (suite compose update).

Environments

Environment Where it runs Audience Image tag (post-B10)
Development Local workstation (dotnet run from the repo root, or docker run against a local PG) Engineers none — built ad-hoc
Edge production Each customer-owned edge device (Jetson Orin / OrangePI / operator-PC), one container per device Operators, autopilot, UI azaion/missions:dev-arm / stage-arm / main-arm per device tier

There is no centralized staging environment in the suite. Each edge device is its own deployment; the stage image tag is for pre-prod customer demo devices, not for a separate cloud staging cluster.

Configuration sources (precedence)

Program.cs resolves each setting in this order:

  1. IConfiguration (defaults: appsettings.json + ASPNETCORE_-prefixed env vars)
  2. Environment.GetEnvironmentVariable(...) (legacy fallback for unprefixed env)
  3. Hardcoded dev fallback (last resort)

DATABASE_URL

Environment Value source Resolved value
Development (no env set) hardcoded fallback Host=localhost;Database=azaion;Username=postgres;Password=changeme
Development (env set) env var Whatever the engineer sets (URL form OR raw Npgsql key=value form both work)
Edge production env var passed via docker compose postgresql://postgres:${PG_LOCAL_PASSWORD}@postgres-local/azaion (per ../../../suite/_docs/00_top_level_architecture.md)

ConvertPostgresUrl helper parses the URL form into Npgsql key=value form. Does NOT URL-decode user/password — credentials with @, :, /, % will be mis-parsed. 07_host Caveats #4 calls this out. Mitigation: avoid those characters in the local PG password (the standard suite provisioning does), or pass a raw Npgsql key=value string instead of a URL.

JWT_SECRET

Environment Value source Resolved value
Development (no env set) hardcoded fallback development-secret-key-min-32-chars!! (well-known; never use in production)
Development (env set) env var Whatever the engineer sets
Edge production env var (compose env_file or per-device-provisioned secret) The shared HMAC secret used by admin + every backend service on the device. Rotation is suite-coordinated — see architecture.md § Security

Critical foot-gun (ADR-005 carry-forward): there is no runtime gate that blocks startup with the dev fallback in production. A misconfigured production deploy will silently boot with the well-known dev secret. The CMMC L2 scorecard tracks the broader fix at suite level (AZ-487 / AZ-494).

Other configuration

These settings are NOT environment-overridable today (carry-forward improvements):

Setting Current behavior Should it differ between dev and prod?
Swagger UI mount Always on Yes — production should gate on IsDevelopment() (ADR-005)
CORS policy AllowAnyOrigin/Method/Header always Yes — production should restrict to the suite's reverse-proxy origin
HTTPS redirection None — assumes upstream TLS termination No — the suite's reverse proxy handles TLS; this is correct
Logging verbosity ASP.NET Core defaults (Information+) Probably not at this scale; LogLevel:Default = Warning could be useful in prod
Migrator DROP TABLE IF EXISTS (B9 one-shot) Runs every startup; idempotent on already-cleaned devices No — the idempotent design means this is safe everywhere

Edge compose excerpt (suite-wide pattern, post-B10)

Per ../../../suite/_docs/00_top_level_architecture.md § Edge compose:

services:
  missions:                                       # was: flights (pre-B10)
    image: ${REGISTRY_HOST}/azaion/missions:${BRANCH:-main}-arm
    container_name: missions
    restart: unless-stopped
    depends_on:
      postgres-local:
        condition: service_healthy
    environment:
      DATABASE_URL: postgresql://postgres:${PG_LOCAL_PASSWORD}@postgres-local/azaion
      JWT_SECRET:   ${JWT_SECRET}
    ports:
      - "5002:8080"
    networks:
      - azaion-edge

The actual file lives in the suite repo (../../../suite/_infra/_compose/) — the snippet here is illustrative.

Secrets management

  • Local dev: hardcoded fallbacks in Program.cs (the dev-secret values listed above).
  • Edge production: env vars sourced from a per-device .env file (created at provisioning) OR a local secrets manager (per-customer choice — the suite supports both patterns). The JWT_SECRET is suite-wide (one value across all backend services on the device).
  • Rotation: changing JWT_SECRET invalidates every issued token until new ones are minted. Coordinated procedure across the device's backend services + UI re-login. There is no online rotation — every backend must be restarted with the new secret simultaneously.

Network / port layout

  • Container EXPOSE 8080; bound HTTP only (no TLS in this service).
  • Edge compose maps 5002:8080 per the suite convention.
  • Reverse proxy (Caddy fronting the suite per ../../../suite/_docs/00_top_level_architecture.md) terminates TLS upstream.
  • No outbound network calls except to postgres-local (DB).

Restart / lifecycle

  • Container restart policy: unless-stopped in production compose (manually-stopped containers stay stopped; crash → auto-restart).
  • Watchtower polls the registry and triggers re-create on new image digest.
  • flight-gate (suite component) gates container restart so it does not happen mid-mission. Once the active mission completes, Watchtower's queued restart goes through.
  • missions itself does not implement graceful shutdown beyond ASP.NET Core's defaults — in-flight HTTP requests are allowed to complete; idle connections are closed.

Backup / disaster recovery

  • Out of scope for this service. PostgreSQL backup is a per-device, suite-level concern (not per-service). Each edge device runs postgres-local; backup cadence and offsite replication are decided at provisioning time and documented at suite level (currently informally).
  • No application-level export — the only way to read mission / waypoint data out of the device is through the API or pg_dump against postgres-local.