mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-22 10:01: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,140 @@
|
||||
# JWT iss/aud validation
|
||||
|
||||
**Task**: AZ-494_jwt_iss_aud_validation
|
||||
**Name**: JWT iss/aud validation
|
||||
**Description**: Flip `ValidateIssuer` and `ValidateAudience` from `false` to `true` in `AddSatelliteJwt`, configure the expected `iss` / `aud` values via configuration, and update Bearer-token consumers and tests to mint tokens with matching claims. Closes Medium-severity finding F-AUTH-2 from the cycle-2 security audit.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: AZ-487 (extends `AddSatelliteJwt` configuration); external dependency: admin team must confirm the expected `iss` / `aud` values before implementation can begin.
|
||||
**Component**: WebApi (`SatelliteProvider.Api/Authentication`) + Test infrastructure
|
||||
**Tracker**: AZ-494
|
||||
**Epic**: none (cycle-3 security hardening)
|
||||
|
||||
## Problem
|
||||
|
||||
The cycle-2 security audit (`_docs/05_security/security_report.md`, finding F-AUTH-2) noted that `AddSatelliteJwt` currently sets `ValidateIssuer = false` and `ValidateAudience = false` (`SatelliteProvider.Api/Authentication/AuthenticationServiceCollectionExtensions.cs`). The cycle-2 deploy report R3 carries the same item as an open follow-up.
|
||||
|
||||
This was intentional during AZ-487 — the suite-level auth contract (`suite/_docs/10_auth.md`) does not currently mandate specific `iss`/`aud` values, and the admin team had not yet decided what those values should be. The mitigation in cycle 2 was: signature + lifetime validation alone are sufficient to keep this finding at Medium, not High. But the door is left open for token reuse across other satellite-suite services that share the same `JWT_SECRET` — a service-specific `aud` claim is the proper remediation.
|
||||
|
||||
OWASP A07 (`_docs/05_security/owasp_review.md`) lists iss/aud validation as part of the JWT hardening backlog; this PBI closes the remaining gap.
|
||||
|
||||
**Prerequisite: cross-team input required.** Before implementation can begin, the admin team must confirm:
|
||||
1. The exact `iss` value the admin API stamps into tokens (e.g., `https://admin.azaion.example/`).
|
||||
2. The `aud` value satellite-provider should require (e.g., `satellite-provider` or a URI).
|
||||
|
||||
If the admin team has not provided these values when the PBI is picked up, the implementer STOPS and surfaces the blocker to the user. No fallback / placeholder value should be hardcoded.
|
||||
|
||||
## Outcome
|
||||
|
||||
- `AddSatelliteJwt` accepts `ValidIssuer` and `ValidAudience` from configuration (env var `JWT_ISSUER` / `JWT_AUDIENCE` preferred; `Jwt:Issuer` / `Jwt:Audience` config keys as fallback).
|
||||
- `TokenValidationParameters` sets `ValidateIssuer = true`, `ValidateAudience = true`, `ValidIssuer = <configured>`, `ValidAudience = <configured>`.
|
||||
- Startup fails fast if `JWT_ISSUER` or `JWT_AUDIENCE` is unset — same contract as `JWT_SECRET` (cycle-2 AC-5 of AZ-487).
|
||||
- Test fixtures (consolidated JWT factory, or per-project helpers if `01_consolidate_jwt_test_helpers` hasn't shipped yet) mint tokens with matching `iss` / `aud` claims so existing tests continue to pass.
|
||||
- `appsettings.Development.json` includes clearly-tagged dev values for `Jwt:Issuer` and `Jwt:Audience`.
|
||||
- `.env.example` documents the two new env vars.
|
||||
- `docker-compose.yml` + `docker-compose.tests.yml` forward `JWT_ISSUER` / `JWT_AUDIENCE` to both the API and the test runner.
|
||||
- `_docs/05_security/security_report.md` and `owasp_review.md` are updated: F-AUTH-2 status moves from `Open` to `Resolved` with a reference to this PBI.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
- Extend `AuthenticationServiceCollectionExtensions.AddSatelliteJwt` to read `JWT_ISSUER` / `JWT_AUDIENCE` from env / config and assign them to `TokenValidationParameters`.
|
||||
- Add fail-fast validation: throw `InvalidOperationException` at startup if either value is missing or whitespace-only.
|
||||
- Update test fixtures to mint tokens with the configured `iss` / `aud`:
|
||||
- `JwtTokenFactory.Create` (or whatever shape exists post-`01_consolidate_jwt_test_helpers`) gains optional `issuer` / `audience` parameters with sensible test defaults.
|
||||
- `SatelliteProvider.IntegrationTests/Program.cs` reads `JWT_ISSUER` / `JWT_AUDIENCE` from env and passes them to the default token.
|
||||
- Update `appsettings.json` (empty placeholders), `appsettings.Development.json` (DEV-ONLY values).
|
||||
- Update `.env.example` and `scripts/run-tests.sh` to load the two new env vars from `.env`.
|
||||
- Update `_docs/02_document/modules/api_program.md` § Security and `architecture.md` § Security Architecture to document the iss/aud validation.
|
||||
- Update `_docs/05_security/security_report.md` (mark F-AUTH-2 Resolved) and `owasp_review.md` § A07 (update the iss/aud bullet).
|
||||
- Coordinate write-back: append a one-line "iss/aud validation enabled" entry to `suite/_docs/10_auth.md` if (and only if) the suite-level contract needs an update — open question for the admin team.
|
||||
|
||||
### Excluded
|
||||
|
||||
- Any change to the signing algorithm (still HS256).
|
||||
- Any change to the `JWT_SECRET` validation, clock skew, or `RequireSignedTokens` semantics.
|
||||
- Adding `nbf` (not-before) validation — already implicitly handled by `JwtSecurityToken`.
|
||||
- Token expiry duration changes.
|
||||
- Multi-tenant token validation (multiple valid issuers / audiences) — out of scope unless the admin team's confirmed values require it.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Issuer validation enforced**
|
||||
Given the API is running with `JWT_ISSUER=https://admin.example/`
|
||||
When a request arrives with a Bearer token whose `iss` claim is `https://other.example/`
|
||||
Then the request returns `401 Unauthorized` with the `WWW-Authenticate` header indicating issuer mismatch.
|
||||
|
||||
**AC-2: Audience validation enforced**
|
||||
Given the API is running with `JWT_AUDIENCE=satellite-provider`
|
||||
When a request arrives with a Bearer token whose `aud` claim is `mission-planner`
|
||||
Then the request returns `401 Unauthorized` with the `WWW-Authenticate` header indicating audience mismatch.
|
||||
|
||||
**AC-3: Matching iss + aud accepted**
|
||||
Given the API is running with configured `JWT_ISSUER` and `JWT_AUDIENCE`
|
||||
When a request arrives with a Bearer token whose `iss` and `aud` claims match exactly
|
||||
Then the request is authenticated and the existing endpoint handler is reached.
|
||||
|
||||
**AC-4: Missing config fails fast**
|
||||
Given the API starts without `JWT_ISSUER` (or with empty value)
|
||||
When the application boots
|
||||
Then it throws `InvalidOperationException` with a clear message naming the missing env var.
|
||||
|
||||
**AC-5: Existing tests pass with matched fixtures**
|
||||
Given the full integration suite is updated to mint tokens with the configured `iss`/`aud`
|
||||
When the suite runs
|
||||
Then all scenarios pass.
|
||||
|
||||
**AC-6: Security artifacts updated**
|
||||
Given the post-PBI repo
|
||||
When `_docs/05_security/security_report.md` is read
|
||||
Then F-AUTH-2 is marked Resolved with a reference to this PBI's tracker ID.
|
||||
|
||||
**AC-7: Suite contract reflects reality**
|
||||
Given the admin team's confirmed values
|
||||
When implementation lands
|
||||
Then `suite/_docs/10_auth.md` either documents the values or explicitly notes that satellite-provider validates them locally without dictating suite-wide values.
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
**Security**
|
||||
- The configured `JWT_ISSUER` / `JWT_AUDIENCE` are NOT secrets — they can be public. But they MUST be operator-configurable so a production rotation (e.g., admin API URL change) does not require a code change.
|
||||
|
||||
**Compatibility**
|
||||
- Backwards-compatible with existing token consumers ONLY IF those consumers update their token-issuance to include the new claims. This is a coordinated rollout — see Risk 1.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
| AC Ref | What to Test | Required Outcome |
|
||||
|--------|-------------|-----------------|
|
||||
| AC-1 | `TokenValidationParameters.ValidIssuer` matches configured value | Returns the configured value |
|
||||
| AC-2 | `TokenValidationParameters.ValidAudience` matches configured value | Returns the configured value |
|
||||
| AC-4 | `AddSatelliteJwt` with `JWT_ISSUER` unset | Throws `InvalidOperationException` with `JWT_ISSUER` in message |
|
||||
| AC-4 | `AddSatelliteJwt` with `JWT_AUDIENCE` unset | Throws `InvalidOperationException` with `JWT_AUDIENCE` in message |
|
||||
|
||||
## Blackbox Tests
|
||||
|
||||
| AC Ref | Initial Data/Conditions | What to Test | Expected Behavior | NFR References |
|
||||
|--------|------------------------|-------------|-------------------|----------------|
|
||||
| AC-1 | API running with `JWT_ISSUER` set; token minted with wrong `iss` | `GET /api/satellite/tiles/latlon` with the wrong-iss token | 401 Unauthorized | Security |
|
||||
| AC-2 | API running with `JWT_AUDIENCE` set; token minted with wrong `aud` | Same as AC-1 with wrong `aud` | 401 Unauthorized | Security |
|
||||
| AC-3 | API running with matching values; token minted with matching `iss` + `aud` | Same probe | 200 OK | — |
|
||||
|
||||
## Constraints
|
||||
|
||||
- The configured `JWT_ISSUER` and `JWT_AUDIENCE` must come from environment / configuration — never hardcoded in source.
|
||||
- Test environment uses dev-only values clearly tagged (e.g., `DEV-ONLY-iss-…`).
|
||||
- Coordinated rollout: admin team confirms iss / aud values BEFORE implementation begins.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1: Cross-team coordination delay**
|
||||
- *Risk*: Admin team has not yet decided on `iss` / `aud` values. Implementer is blocked.
|
||||
- *Mitigation*: This PBI is explicitly gated on cross-team input. The blocker is recorded as a hard prerequisite in the task header. If the admin team is unresponsive after a reasonable window, this PBI is parked, not force-completed with placeholder values.
|
||||
|
||||
**Risk 2: Coordinated breakage of existing token consumers**
|
||||
- *Risk*: `gps-denied-onboard` and the mission-planner UI currently mint tokens whose `iss` / `aud` may not match what we configure. Flipping the validation flag breaks them.
|
||||
- *Mitigation*: Coordinate the rollout with consumer teams BEFORE this PBI ships to production. Stage in `dev` first; verify consumer-side tokens still authenticate. The deploy report (analogous to cycle-2 R1) will flag this risk.
|
||||
|
||||
**Risk 3: Future multi-tenant support**
|
||||
- *Risk*: At some point we may need to accept tokens from multiple issuers (e.g., legacy + new admin API). Hardcoding a single `ValidIssuer` complicates that.
|
||||
- *Mitigation*: The configuration shape allows array values (`TokenValidationParameters.ValidIssuers`) if/when needed. This PBI implements the single-value case; the upgrade path is straightforward.
|
||||
Reference in New Issue
Block a user