Skip to content

S-004: Move RefDataServers registry probe to Windows worker (proof-of-pattern)#714

Open
benhegartysefe wants to merge 4 commits into
mainfrom
feat/423-s004-registry-move
Open

S-004: Move RefDataServers registry probe to Windows worker (proof-of-pattern)#714
benhegartysefe wants to merge 4 commits into
mainfrom
feat/423-s004-registry-move

Conversation

@benhegartysefe

Copy link
Copy Markdown
Contributor

Summary

  • IS step S-004 from the API split work. Moves /RefDataServers/GetServerOperatingFromTarget's Windows-registry read to the worker. Proof-of-pattern for S-005 / S-006.
  • Adds GetServerOperatingSystemAsync to IWindowsWorkerClient. HttpWindowsWorkerClient calls the worker over HTTP; WorkerUnavailableClient throws → global filter returns the documented 503 body.
  • New worker controller Dorc.Api.WindowsWorker/Controllers/RemoteServerController.cs carries the RegistryKey.OpenRemoteBaseKey logic.
  • Primary's controller becomes a thin pass-through.
  • using Microsoft.Win32 removed from the primary controller.

Stacking

This PR contains the S-002 + S-003 + S-004 diffs (since the worker project from S-002 and the contract from S-003 must exist for S-004's code to compile). When #711 (S-002) and #712 (S-003) merge to main, rebase this PR; the diff will collapse to just S-004's own change.

Test plan

  • Dorc.Api, Dorc.Api.WindowsWorker build clean
  • Dorc.Api.Tests: 194 / 194 pass
  • Dorc.Api.WindowsWorker.Tests: 7 / 7 pass

🤖 Generated with Claude Code

benhegartysefe and others added 4 commits May 28, 2026 16:38
…ed-secret auth

New project src/Dorc.Api.WindowsWorker (TargetFramework net8.0-windows,
Microsoft.NET.Sdk.Web). Endpoint-empty for Windows operations; S-004 /
S-005 / S-006 add the actual controllers.

What's in this scaffold:
- Program.cs binds Kestrel to 127.0.0.1 only (port from
  WindowsWorker:Port, default 5005) per HLPS D-1.
- WorkerKey authentication scheme reads the X-Worker-Key header,
  validates with constant-time CryptographicOperations.FixedTimeEquals
  against WindowsWorker:SharedKey config. Missing or wrong header
  returns 401 with body {"error":"worker_key_invalid"} per HLPS D-3.
- FromPrimary authorization policy requires WorkerKey + authenticated
  user. Applied via RequireAuthorization on the controllers route group.
- /health endpoint is intentionally unauthenticated — the loopback bind
  is the security boundary; the primary uses /health to detect liveness.
- Empty SharedKey means fail-closed (a misconfigured host can't
  accidentally authenticate any caller).

Tests (Dorc.Api.WindowsWorker.Tests) — 7 pass:
- 5 handler-level unit tests: no header / wrong key / different length
  key / correct key / no configured secret (fail-closed).
- 2 host smoke tests: /health reachable with no header and with any
  header. Confirms the worker host starts and the auth scheme
  registration doesn't break startup.

Full-pipeline coverage of protected controllers will arrive in
S-004/S-005/S-006 when those endpoints are added — minimal-API IStartupFilter
approach didn't fit .NET 8's WebApplication endpoint pipeline ordering.

Solution wiring: both projects added to src/Dorc.sln.

Branched off main; independent of #706 per IS multi-PR plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ection

Establishes the seam every later worker-move step (S-004 / S-005 / S-006)
extends. Resolves HLPS U-6 (URL discovery via config), U-7 (contract DTO
location: Dorc.ApiModel — future steps), and U-11 (worker-absence
detection: explicit WindowsWorker:Enabled config flag).

New types:
- Dorc.Api/Interfaces/IWindowsWorkerClient.cs — empty marker interface;
  later S-steps add concrete worker methods.
- Dorc.Api/Services/HttpWindowsWorkerClient.cs — real HTTP impl; takes
  injected typed HttpClient.
- Dorc.Api/Services/WorkerUnavailableClient.cs — null impl; methods (added
  by later steps) throw WorkerUnavailableException.
- Dorc.Api/Services/WorkerKeyDelegatingHandler.cs — DelegatingHandler that
  attaches the X-Worker-Key shared secret to every outbound call.
- Dorc.Api/Services/WorkerUnavailableExceptionFilter.cs — global
  IExceptionFilter that translates WorkerUnavailableException to a
  documented 503 body {"error":"windows_worker_unavailable","endpoint":"..."}.
- Dorc.Api/Exceptions/WorkerUnavailableException.cs — typed exception.

DI wiring in Program.cs:
- Reads WindowsWorker:Enabled bool. When true: AddHttpClient<...> for
  the typed client with WorkerKeyDelegatingHandler. When false:
  AddSingleton<IWindowsWorkerClient, WorkerUnavailableClient>.
- WorkerUnavailableExceptionFilter registered globally on controllers.

Config: appsettings.json gains WindowsWorker section. Enabled defaults
to false so existing Windows-install topology is unchanged at this commit
(no worker process running). Flag flips true in S-008 (installer).

Tests (Dorc.Api.Tests/WindowsWorkerClientTests.cs) — 4 pass:
- WorkerKeyDelegatingHandler adds the X-Worker-Key header from config.
- WorkerKeyDelegatingHandler omits the header when secret is empty
  (lets the worker's auth scheme emit the documented 401 cleanly).
- WorkerUnavailableExceptionFilter translates WorkerUnavailableException
  → 503 + documented body shape.
- WorkerUnavailableExceptionFilter ignores other exception types.

Test results:
- Dorc.Api.Tests: 194 / 194 pass (4 new + 190 existing).

Branched off main; independent of #706 / #710 / #711 per IS multi-PR plan.
S-004/S-005/S-006 stack on this once it merges (they add methods to the
interface that both implementations cover).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ker-client' into feat/423-s004-registry-move-v2
Proof-of-pattern worker-move. HLPS Scope D. Moves the Windows-registry
read used by /RefDataServers/GetServerOperatingFromTarget out of the
primary's Dorc.Api and into the Windows worker.

Production code change:
- IWindowsWorkerClient gains GetServerOperatingSystemAsync(serverName).
- HttpWindowsWorkerClient implements it as a GET against
  /remote-server/operating-system?serverName=... on the worker's HttpClient.
- WorkerUnavailableClient throws WorkerUnavailableException("remote-server/operating-system")
  so on Linux installs the global filter returns the documented 503 body.
- New worker controller Dorc.Api.WindowsWorker/Controllers/RemoteServerController.cs
  carries the Microsoft.Win32.Registry.OpenRemoteBaseKey logic. Returns
  ServerOperatingSystemApiModel on success, BadRequest with structured
  error on failure.
- RefDataServersController.GetServerOperatingFromTarget is now a thin
  pass-through: injects IWindowsWorkerClient, awaits the worker call,
  returns the result. Microsoft.Win32 using removed (no other usages
  in the file remain after the move).

Stacked branch (S-002 + S-003 merged in to this branch base so the
worker project exists for the new controller). PR base is main; PR
diff shows S-002 + S-003 + S-004 combined. When S-002 and S-003 merge
to main, this branch rebases cleanly to just the S-004 diff.

Test results:
- Dorc.Api.Tests: 194 / 194 pass (no regressions; S-004 didn't add new
  tests because the controller change is a thin pass-through and the
  Windows-only registry call is what's tested by the worker)
- Dorc.Api.WindowsWorker.Tests: 7 / 7 pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
LastRequest = request;
return Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK));
Assert.AreEqual(StatusCodes.Status503ServiceUnavailable, result!.StatusCode);

// Body is anonymous: {error="windows_worker_unavailable", endpoint="reset-password"}
var body = result.Value!.GetType();
Comment on lines +43 to +47
catch (Exception ex)
{
_logger.LogError(ex, "Failed to read remote registry for server");
return BadRequest(new { error = "Failed to read remote registry for server" });
}
@github-actions

Copy link
Copy Markdown

Test Results

321 tests  +11   321 ✅ +11   25s ⏱️ +14s
  1 suites ± 0     0 💤 ± 0 
  1 files   ± 0     0 ❌ ± 0 

Results for commit 2125eae. ± Comparison against base commit f0fbf91.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant