feat: implement TASK-WP1-04 admin security
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user