S-004: Move RefDataServers registry probe to Windows worker (proof-of-pattern)#714
Open
benhegartysefe wants to merge 4 commits into
Open
S-004: Move RefDataServers registry probe to Windows worker (proof-of-pattern)#714benhegartysefe wants to merge 4 commits into
benhegartysefe wants to merge 4 commits into
Conversation
…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" }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/RefDataServers/GetServerOperatingFromTarget's Windows-registry read to the worker. Proof-of-pattern for S-005 / S-006.GetServerOperatingSystemAsynctoIWindowsWorkerClient.HttpWindowsWorkerClientcalls the worker over HTTP;WorkerUnavailableClientthrows → global filter returns the documented 503 body.Dorc.Api.WindowsWorker/Controllers/RemoteServerController.cscarries theRegistryKey.OpenRemoteBaseKeylogic.using Microsoft.Win32removed 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.WindowsWorkerbuild cleanDorc.Api.Tests: 194 / 194 passDorc.Api.WindowsWorker.Tests: 7 / 7 pass🤖 Generated with Claude Code