feat: implement TASK-WP1-04 admin security

This commit is contained in:
suyx
2026-07-28 18:32:50 +08:00
parent 66fe3b763a
commit 03f1509de7
29 changed files with 2344 additions and 40 deletions
+33 -1
View File
@@ -32,6 +32,7 @@ internal static class Program
{
var security = await TestCredentialBoundaryAsync();
var supervisor = await TestSupervisorLifecycleAsync();
TestSecureConfigurationPersistence();
TestStructuredLogging();
WriteEvidence(Environment.GetEnvironmentVariable("DADA_EVIDENCE_DIR_SEC"), security);
WriteEvidence(Environment.GetEnvironmentVariable("DADA_EVIDENCE_DIR_SUP"), supervisor);
@@ -74,16 +75,46 @@ internal static class Program
}
}
private static void TestSecureConfigurationPersistence()
{
var directory = Path.Combine(Path.GetTempPath(), $"dada-secure-config-{Guid.NewGuid():N}");
var path = Path.Combine(directory, "instance.json");
var email = "synthetic-admin@example.invalid";
var pepper = $"synthetic-pepper-{Guid.NewGuid():N}";
var digest = Convert.ToHexString(System.Security.Cryptography.HMACSHA256.HashData(
System.Text.Encoding.UTF8.GetBytes(pepper),
System.Text.Encoding.UTF8.GetBytes(email)));
try
{
var store = new InstanceConfigurationStore(path);
store.Save(new InstanceConfiguration(1, 7, null, null, [digest], [digest]));
var raw = File.ReadAllText(path);
True(raw.Contains("\"secure_config_revision\": 7", StringComparison.Ordinal), "secure config revision field");
True(raw.Contains("\"schema_version\": 1", StringComparison.Ordinal), "secure config schema field");
False(raw.Contains(email, StringComparison.OrdinalIgnoreCase), "allowlist email persisted");
False(raw.Contains(pepper, StringComparison.Ordinal), "admin pepper persisted");
False(File.Exists(path + ".tmp"), "secure config temporary file residual");
var loaded = store.Load();
Equal(7, loaded.Revision, "secure config revision round trip");
EqualSequence(new[] { digest }, loaded.AdminRecoveryHashes, "secure config recovery marker");
}
finally
{
if (Directory.Exists(directory)) Directory.Delete(directory, recursive: true);
}
}
private static async Task<object> TestCredentialBoundaryAsync()
{
var store = new TestCredentialStore();
var marker = $"wp0-{Guid.NewGuid():N}";
store.Write(CredentialCatalog.ApiResend, marker + "-mail");
store.Write(CredentialCatalog.ApiAmap, marker + "-map");
store.Write(CredentialCatalog.AdminPepper, marker + "-admin");
store.Write(CredentialCatalog.WorkerAiGateway, marker + "-ai");
var apiProbe = await LaunchCredentialProbeAsync(ChildRole.Api, store);
EqualSequence(new[] { CredentialCatalog.ApiAmap, CredentialCatalog.ApiResend }, apiProbe.Names.Order().ToArray(), "API credential scope");
EqualSequence(new[] { CredentialCatalog.AdminPepper, CredentialCatalog.ApiAmap, CredentialCatalog.ApiResend }, apiProbe.Names.Order().ToArray(), "API credential scope");
False(apiProbe.EnvironmentContainsMarker, "credential leaked into child environment");
False(apiProbe.ArgumentsContainMarker, "credential leaked into child arguments");
@@ -192,6 +223,7 @@ internal static class Program
var childStore = new TestCredentialStore();
childStore.Write(CredentialCatalog.ApiResend, $"probe-{Guid.NewGuid():N}-mail");
childStore.Write(CredentialCatalog.ApiAmap, $"probe-{Guid.NewGuid():N}-map");
childStore.Write(CredentialCatalog.AdminPepper, $"probe-{Guid.NewGuid():N}-admin");
var childStart = new ProcessStartInfo(Environment.ProcessPath!);
childStart.ArgumentList.Add("--managed-child");
var managed = await ManagedChildProcess.StartAsync(childStart, ChildRole.Api, childStore);