Files

4.7 KiB

NFR Requirements — Unit 2: slave-availability-extension

Security

SEC-01: ApiKey encrypted at rest (Q1=C)

The MasterRegistration.ApiKey field is not stored in plain text. The slave encrypts the key using ASP.NET Core Data Protection before writing to the DB, and decrypts it before comparison on each incoming request.

Aspect Decision
Mechanism ASP.NET Core Data Protection (IDataProtectionProvider)
Wrapper type IMasterApiKeyProtector / MasterApiKeyProtector
Purpose string "SlpModularCms.Availability.MasterApiKey"
Comparison Decrypt stored key → compare with incoming header value (plain-text equality)
On decryption failure Return null (key is unreadable) → treat as mismatch → 401

Rationale: Consistent with Unit 1's IApiKeyProtector pattern. Protects the key if the database is accessed directly (e.g., backup file, DB dump). The purpose string isolates it from Unit 1's protector scope.

SEC-02: Missing or empty header → 401

An absent or empty X-Master-Api-Key header on any master endpoint (register, status, registered-url) immediately returns 401 without touching the database. No information leaked about whether a registration exists.

SEC-03: Unauthenticated controller, API key validated in service

The three master endpoints carry no [Authorize] attribute — authentication is done via the custom header in MasterAvailabilityService. The endpoints are in the /api/v1/master/ bypass prefix so the availability gate cannot block them.


Performance

PERF-01: Master gate check is synchronous

The AvailabilityMiddleware reads _masterIsAvailable from a static volatile field — zero async overhead, zero DB call per request. Only status pushes touch the DB.

PERF-02: No DB call on gate evaluation

The master gate evaluates solely from volatile static fields. The local gate still calls IAvailabilityService.IsAvailableAsync() (one DB read with 1s cache per AvailabilityOptions.StatusCacheSeconds — unchanged from existing behaviour).


Reliability

REL-01: Fail-open on process startup (Q2=A, Q4=A from FD)

_masterIsAvailable defaults to true at process startup. A slave that restarts before the master pushes status again is immediately accessible. The master's IntegrityCheckBackgroundService will push status again within IntegrityCheckIntervalMinutes.

REL-02: No expiry on cached status (Q4=A from FD)

The static cache has no TTL. If the master goes offline permanently, the last pushed status is used indefinitely. For a slave last told "unavailable", it remains unavailable until either:

  • The master recovers and pushes "available" again, or
  • An operator manually calls POST /api/v1/Availability/status locally (existing AvailabilityController endpoint, Owner-only).

REL-03: Static field thread safety via volatile (Q2=A)

private static volatile bool _masterIsAvailable and private static volatile string? _masterDisableMessage. Writes to reference types and bools are atomic in .NET; volatile ensures cross-thread visibility without a lock on every request. Consistent with PersistentAvailabilityService's _lastErrorTime pattern.


Maintainability

MAINT-01: Test coverage ≥ 80% (Q3=B)

Excluded from coverage:

  • AvailabilityModule (service registration changes + Database.MigrateAsync() call)
  • EF Core migration files (auto-generated)
  • DTO / request / response record classes ([ExcludeFromCodeCoverage])

Included (must reach ≥ 80%):

  • MasterController
  • MasterAvailabilityService (all 3 public methods)
  • MasterApiKeyProtector
  • AvailabilityMiddleware (extended paths — master gate logic)
  • IMasterRegistrationRepository / MasterRegistrationRepository

MAINT-02: Structured logging (Q4=B)

Scenario Level Structured Fields
First master registration Information masterUrl
Re-registration (existing key match) Information masterUrl
API key mismatch on any endpoint Warning endpoint (do NOT log the key itself)
Missing X-Master-Api-Key header Warning endpoint
Status update received Information isAvailable, disableMessage
Master gate blocked a request Warning path, disableMessage
Get-registered-url called Debug
Decryption failure on stored key Error (no key value)

Test Framework (unchanged from Unit 1)

Aspect Decision
Framework xUnit
Mocking NSubstitute 5.x
Assertions FluentAssertions 8.x
EF Core testing EF Core InMemory provider
Data Protection testing EphemeralDataProtectionProvider
Project name SlpModularCms.Modules.Availability.Master.Tests