mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 21:51:15 +00:00
[AZ-494] Enable JWT iss/aud validation with fail-fast startup
Option B per user decision: production ships with empty Jwt.Issuer / Jwt.Audience in appsettings.json so the API process refuses to start unless JWT_ISSUER + JWT_AUDIENCE env vars are supplied. Development ships with grep-friendly DEV-ONLY- placeholders so local + docker flows keep working unchanged. AuthenticationServiceCollectionExtensions flips ValidateIssuer + ValidateAudience to true and wires ValidIssuer / ValidAudience via a new ResolveRequiredOrThrow helper that all three required values (secret, iss, aud) now share. JwtTokenFactory.Create + CreateExpired gain optional iss / aud parameters (default null) so existing call sites compile unchanged. JwtTestHelpers adds MintAuthenticated / MintExpired wrappers that resolve iss + aud from env, plus ResolveIssuerOrThrow / ResolveAudienceOrThrow. PerfBootstrap.MintToken + Program.cs JWT bootstrap migrated to the new surface so the perf harness and the integration runner both validate against the same contract. Adds 4 fail-fast unit tests (missing/empty issuer + audience), 2 negative integration scenarios (WrongIssuer_Returns401, WrongAudience_Returns401), and re-tags every existing integration mint site via MintAuthenticated. Compose, .env.example, run-tests.sh, run-performance-tests.sh all load + export JWT_ISSUER + JWT_AUDIENCE alongside JWT_SECRET. Resolves F-AUTH-2 (security_report.md + owasp_review.md). AC-7 (cross-repo suite/_docs/10_auth.md write) deferred — outside this workspace; tracked in deploy_cycle2.md R3 follow-up. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
# Batch Report — Batch 05 cycle 3
|
||||
|
||||
**Batch**: 05 (cycle 3)
|
||||
**Tasks**: AZ-494 (JWT iss/aud validation — enable + configure)
|
||||
**Date**: 2026-05-12
|
||||
|
||||
## Task Results
|
||||
|
||||
| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|
||||
|------|--------|---------------|-------|-------------|--------|
|
||||
| AZ-494_jwt_iss_aud_validation | Done (Option B) | 1 prod source + 1 TestSupport + 4 IntegrationTests + 1 unit test + 4 config + 4 scripts/compose + 6 docs | 4 new unit tests + 2 new integration scenarios; existing 13 unit + 8 integration cases re-tagged to mint via env iss/aud | 6/7 ACs addressed; AC-7 deferred (cross-repo `suite/_docs/10_auth.md` write) | 0 blockers; 1 Low (cross-repo doc), 1 acknowledged operational gate (admin team must supply real prod iss/aud — fail-fast at deploy enforces this). |
|
||||
|
||||
## AC Test Coverage: 6 of 7 addressed (AC-7 deferred — cross-repo)
|
||||
## Code Review Verdict: pending (this batch report precedes per-batch review)
|
||||
## Auto-Fix Attempts: 0
|
||||
## Stuck Agents: None
|
||||
|
||||
## What was implemented
|
||||
|
||||
The task spec offered three options for handling the blocker (admin team has not yet confirmed production `iss` / `aud` values). The user selected **Option B**: implement the full validation plumbing now with **clearly-tagged DEV-only values** in `appsettings.Development.json` so local tests work, but leave `appsettings.json` empty so production deploys without explicit `JWT_ISSUER` / `JWT_AUDIENCE` environment variables fail at startup, not at runtime.
|
||||
|
||||
The implementation therefore:
|
||||
|
||||
1. Enables `ValidateIssuer = true` and `ValidateAudience = true` in the production token-validation pipeline.
|
||||
2. Sources both values from `JWT_ISSUER` / `JWT_AUDIENCE` env vars with `Jwt:Issuer` / `Jwt:Audience` config keys as fallback (same resolution pattern as `JWT_SECRET` from AZ-487).
|
||||
3. Throws `InvalidOperationException` at startup if either value is missing or whitespace — the message names the env var, the config key, and the AZ-494 task spec.
|
||||
4. Threads `iss` / `aud` through the canonical `SatelliteProvider.TestSupport.JwtTokenFactory.Create` surface (the post-AZ-491 single source of truth) so every existing test path continues to mint matching tokens.
|
||||
5. Adds a thin convenience layer in `SatelliteProvider.IntegrationTests/JwtTestHelpers` (`MintAuthenticated`, `MintExpired`, `ResolveIssuerOrThrow`, `ResolveAudienceOrThrow`) so integration test call sites stay terse and centrally fail-fast on missing env vars.
|
||||
6. Adds two new negative integration tests (`WrongIssuer_Returns401`, `WrongAudience_Returns401`) and four new unit fail-fast tests (`AddSatelliteJwt_ThrowsOnMissingIssuer` / `_ThrowsOnEmptyIssuer` / `_ThrowsOnMissingAudience` / `_ThrowsOnEmptyAudience`).
|
||||
7. Updates security artefacts (`security_report.md` flips F-AUTH-2 to **RESOLVED**, `owasp_review.md` A07 reflects same), the architecture + module docs (`architecture.md`, `modules/api_program.md`, `modules/tests_integration.md`, `modules/tests_unit.md`), the cycle-2 deploy report (R3 follow-up note), and the traceability matrix (5 new rows for AZ-494 AC-1..AC-7).
|
||||
|
||||
### Added
|
||||
|
||||
- `SatelliteProvider.IntegrationTests/JwtTestHelpers.cs` — three new public helpers:
|
||||
- `ResolveIssuerOrThrow()` / `ResolveAudienceOrThrow()` — mirror the existing `ResolveSecretOrThrow` pattern (read env, throw `InvalidOperationException` with a humanised message if missing).
|
||||
- `MintAuthenticated(...)` — convenience wrapper: defaults issuer + audience to the env-resolved values, accepts explicit overrides for negative test cases.
|
||||
- `MintExpired(...)` — convenience wrapper for the existing `JwtTokenFactory.CreateExpired` overload, same env-resolution behaviour.
|
||||
- 4 unit tests in `SatelliteProvider.Tests/Authentication/AuthenticationServiceCollectionExtensionsTests.cs`: `AddSatelliteJwt_ThrowsOnMissingIssuer`, `AddSatelliteJwt_ThrowsOnEmptyIssuer`, `AddSatelliteJwt_ThrowsOnMissingAudience`, `AddSatelliteJwt_ThrowsOnEmptyAudience`.
|
||||
- 2 integration tests in `SatelliteProvider.IntegrationTests/JwtIntegrationTests.cs`: `WrongIssuer_Returns401`, `WrongAudience_Returns401`.
|
||||
|
||||
### Modified
|
||||
|
||||
- `SatelliteProvider.Api/Authentication/AuthenticationServiceCollectionExtensions.cs` — added `JwtIssuerEnvVar` / `JwtIssuerConfigKey` / `JwtAudienceEnvVar` / `JwtAudienceConfigKey` constants; flipped `ValidateIssuer` / `ValidateAudience` to `true` and wired `ValidIssuer` / `ValidAudience`; extracted a single `ResolveRequiredOrThrow` helper that all three required values now flow through.
|
||||
- `SatelliteProvider.TestSupport/JwtTokenFactory.cs` — `Create(...)` and `CreateExpired(...)` gained optional `issuer` / `audience` parameters defaulted to `null` (old call sites still produce identical tokens; new call sites pass real values).
|
||||
- `SatelliteProvider.IntegrationTests/PerfBootstrap.cs` — `MintToken()` now also resolves iss + aud and passes them through to `JwtTokenFactory.Create`. Without this the perf harness's bearer token would fail validation against the AZ-494-hardened API.
|
||||
- `SatelliteProvider.IntegrationTests/Program.cs` — JWT bootstrap now resolves all three required values (secret + iss + aud) inside a single `try/catch` and prints them at startup. The `MintAuthenticated` helper replaces the inline `JwtTokenFactory.Create` call that used to live in `Main`.
|
||||
- `SatelliteProvider.IntegrationTests/JwtIntegrationTests.cs` — `AnonymousRequest_*`, `ExpiredToken_Returns401`, `InvalidSignature_Returns401`, `ValidToken_Returns200_OnHealthyEndpoint` all migrated to `JwtTestHelpers.MintAuthenticated` / `JwtTestHelpers.MintExpired`. Adds the two new scenarios to `RunAll`.
|
||||
- `SatelliteProvider.IntegrationTests/UavUploadTests.cs` — every `JwtTokenFactory.Create(...)` call replaced with `JwtTestHelpers.MintAuthenticated(...)`. Direct `using SatelliteProvider.TestSupport;` dropped (no longer needed at this seam).
|
||||
- `SatelliteProvider.Tests/Authentication/AuthenticationServiceCollectionExtensionsTests.cs` — existing `_ConfiguresTokenValidationParameters_AsPerContract` and `_PrefersEnvironmentVariableOverConfiguration` cases updated to assert the AZ-494 contract; `BuildValidConfiguration()` now seeds iss + aud; static `[Fact]` setup/teardown saves and restores both env vars.
|
||||
- `SatelliteProvider.Api/appsettings.json` — added empty `Jwt.Issuer` and `Jwt.Audience` keys (the fail-fast contract requires env-var or non-empty config; empty here forces ops to supply env vars in prod).
|
||||
- `SatelliteProvider.Api/appsettings.Development.json` — placeholder dev values prefixed `DEV-ONLY-` so a grep for that prefix surfaces every "remember to replace" site.
|
||||
- `.env.example` — documents `JWT_ISSUER` and `JWT_AUDIENCE` with the fail-fast contract and a one-line example value pair (same `DEV-ONLY-` prefix).
|
||||
- `docker-compose.yml` / `docker-compose.tests.yml` — `JWT_ISSUER` and `JWT_AUDIENCE` now passed through to both the `api` and `integration-tests` services.
|
||||
- `scripts/run-tests.sh` / `scripts/run-performance-tests.sh` — `.env` load + fail-fast checks for both new vars mirror the existing `JWT_SECRET` flow; both are exported so Docker Compose and the perf bootstrap see them.
|
||||
|
||||
### Documentation
|
||||
|
||||
- `_docs/02_document/architecture.md` — token contract bullet + Security Architecture authentication paragraph updated.
|
||||
- `_docs/02_document/modules/api_program.md` — JWT authentication section + Configuration section.
|
||||
- `_docs/02_document/modules/tests_integration.md` — env-var prerequisites updated; `JwtIntegrationTests` and `JwtTestHelpers` entries describe the new AZ-494 surface.
|
||||
- `_docs/02_document/modules/tests_unit.md` — `AuthenticationServiceCollectionExtensionsTests` entry now lists the four AZ-494 fail-fast cases plus the updated config-precedence and contract assertions.
|
||||
- `_docs/02_document/tests/traceability-matrix.md` — 5 new rows for AC-1..AC-7 (AC-7 marked deferred); the AZ-487 NFR rows updated to acknowledge the AZ-494 extension.
|
||||
- `_docs/03_implementation/deploy_cycle2.md` — R3 follow-up note marked **RESOLVED in cycle 3 (AZ-494)** with the residual operational gate spelled out.
|
||||
- `_docs/05_security/security_report.md` — F-AUTH-2 flipped to **RESOLVED cycle 3 (AZ-494)**; verdict reconciliation + recommendations updated.
|
||||
- `_docs/05_security/owasp_review.md` — A07 row updated; new Low finding for residual "no token revocation list" gap noted as a separate follow-up.
|
||||
|
||||
## AC verification
|
||||
|
||||
| AC | Description | Verification |
|
||||
|---|---|---|
|
||||
| AC-1 | Wrong `iss` token returns 401 | `JwtIntegrationTests.WrongIssuer_Returns401` (integration; runtime gate at Step 16) |
|
||||
| AC-2 | Wrong `aud` token returns 401 | `JwtIntegrationTests.WrongAudience_Returns401` (integration; runtime gate at Step 16) |
|
||||
| AC-3 | Matching iss + aud accepted | `JwtIntegrationTests.ValidToken_Returns200_OnHealthyEndpoint` retains its assertion; tokens now minted via env-resolved iss/aud through `MintAuthenticated` |
|
||||
| AC-4 | Missing config fails fast | 4 new unit tests in `AuthenticationServiceCollectionExtensionsTests`; manual `docker compose up` without env vars throws `InvalidOperationException` per the contract |
|
||||
| AC-5 | Existing tests pass with matched fixtures | All `JwtTokenFactory.Create` direct call sites in the integration project removed in favour of `MintAuthenticated` (verified via `Grep`); unit suite still mints via the factory with explicit iss/aud |
|
||||
| AC-6 | Security artefacts updated | `security_report.md` + `owasp_review.md` updated this batch |
|
||||
| AC-7 | Suite-level contract reflects validation | **Deferred** — `suite/_docs/10_auth.md` lives in the parent monorepo, outside this workspace. Cross-repo write is out of scope for satellite-provider's autodev. `deploy_cycle2.md` notes the cross-repo obligation. |
|
||||
|
||||
## Static / process checks
|
||||
|
||||
- `dotnet format whitespace --verify-no-changes` will run as part of `scripts/run-tests.sh` at Step 16.
|
||||
- `ReadLints` on every modified C# file returned 0 warnings.
|
||||
- Repo-wide grep for `JwtTokenFactory.Create` confirms only `SatelliteProvider.Tests` (unit, which intentionally exercises the factory directly with explicit iss/aud) + `PerfBootstrap.MintToken` + `JwtTestHelpers.MintAuthenticated` / `MintExpired` call it now — the integration suite never bypasses the env-resolution wrapper.
|
||||
- `.env.example` keeps the `DEV-ONLY-` prefix grep-friendly so a future ops review can surface every placeholder site at once.
|
||||
|
||||
## Risks & follow-ups
|
||||
|
||||
- **Operational gate** (intentional, by Option B) — production deploy WITHOUT `JWT_ISSUER` + `JWT_AUDIENCE` env vars will fail at process start with the `InvalidOperationException` message documented above. This is the controlled deploy-time forcing function for admin-team confirmation.
|
||||
- **Cross-repo doc** (AC-7) — `suite/_docs/10_auth.md` write deferred. Will surface as a `_docs/_process_leftovers/` entry if the suite repo still needs the update after this autodev finishes.
|
||||
Reference in New Issue
Block a user