audit notes work completed, test fixes work (95% done), new sprints, new data sources setup and configuration

This commit is contained in:
master
2026-01-14 10:48:00 +02:00
parent d7be6ba34b
commit 95d5898650
379 changed files with 40695 additions and 19041 deletions

View File

@@ -0,0 +1,156 @@
using Microsoft.Extensions.Configuration;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
namespace StellaOps.Doctor.Plugins.Authority.Checks;
/// <summary>
/// Validates that at least one authority plugin (Standard or LDAP) is configured.
/// </summary>
public sealed class AuthorityPluginConfigurationCheck : IDoctorCheck
{
/// <inheritdoc />
public string CheckId => "check.authority.plugin.configured";
/// <inheritdoc />
public string Name => "Authority Plugin Configuration";
/// <inheritdoc />
public string Description => "Validates that at least one authentication plugin is configured";
/// <inheritdoc />
public DoctorSeverity DefaultSeverity => DoctorSeverity.Fail;
/// <inheritdoc />
public IReadOnlyList<string> Tags => ["authority", "authentication", "configuration", "security"];
/// <inheritdoc />
public TimeSpan EstimatedDuration => TimeSpan.FromMilliseconds(50);
/// <inheritdoc />
public bool CanRun(DoctorPluginContext context) => true;
/// <inheritdoc />
public Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
{
var result = context.CreateResult(CheckId, "stellaops.doctor.authority", DoctorCategory.Authority.ToString());
var configuredPlugins = new List<string>();
var issues = new List<string>();
// Check Standard plugin configuration
var standardEnabled = context.Configuration.GetValue<bool?>("Authority:Plugins:Standard:Enabled");
var standardSection = context.Configuration.GetSection("Authority:Plugins:Standard");
if (standardEnabled == true || standardSection.Exists())
{
configuredPlugins.Add("Standard");
}
// Check LDAP plugin configuration
var ldapEnabled = context.Configuration.GetValue<bool?>("Authority:Plugins:Ldap:Enabled");
var ldapSection = context.Configuration.GetSection("Authority:Plugins:Ldap");
if (ldapEnabled == true || ldapSection.Exists())
{
var ldapServer = context.Configuration.GetValue<string>("Authority:Plugins:Ldap:Server");
if (string.IsNullOrWhiteSpace(ldapServer))
{
issues.Add("LDAP plugin enabled but server not configured");
}
else
{
configuredPlugins.Add("LDAP");
}
}
// Check OIDC plugin configuration
var oidcEnabled = context.Configuration.GetValue<bool?>("Authority:Plugins:Oidc:Enabled");
var oidcSection = context.Configuration.GetSection("Authority:Plugins:Oidc");
if (oidcEnabled == true || oidcSection.Exists())
{
var oidcAuthority = context.Configuration.GetValue<string>("Authority:Plugins:Oidc:Authority");
if (string.IsNullOrWhiteSpace(oidcAuthority))
{
issues.Add("OIDC plugin enabled but authority not configured");
}
else
{
configuredPlugins.Add("OIDC");
}
}
// Check SAML plugin configuration
var samlEnabled = context.Configuration.GetValue<bool?>("Authority:Plugins:Saml:Enabled");
if (samlEnabled == true)
{
configuredPlugins.Add("SAML");
}
if (configuredPlugins.Count == 0)
{
return Task.FromResult(result
.Fail("No authentication plugins configured")
.WithEvidence("Authority configuration", e =>
{
e.Add("ConfiguredPlugins", "(none)");
e.Add("StandardEnabled", standardEnabled?.ToString() ?? "(not set)");
e.Add("LdapEnabled", ldapEnabled?.ToString() ?? "(not set)");
e.Add("OidcEnabled", oidcEnabled?.ToString() ?? "(not set)");
})
.WithCauses(
"No authentication plugin is enabled in configuration",
"Authority:Plugins section is missing or empty",
"Users cannot authenticate without at least one plugin")
.WithRemediation(r => r
.AddStep(1, "Enable Standard authentication",
"# Add to appsettings.json:\n" +
"\"Authority\": {\n" +
" \"Plugins\": {\n" +
" \"Standard\": { \"Enabled\": true }\n" +
" }\n" +
"}",
CommandType.FileEdit)
.AddStep(2, "Or configure LDAP authentication",
"# Add to appsettings.json:\n" +
"\"Authority\": {\n" +
" \"Plugins\": {\n" +
" \"Ldap\": {\n" +
" \"Enabled\": true,\n" +
" \"Server\": \"ldap://your-server\"\n" +
" }\n" +
" }\n" +
"}",
CommandType.FileEdit)
.AddStep(3, "Run setup wizard to configure",
"stella setup --step authority",
CommandType.Shell))
.WithVerification($"stella doctor --check {CheckId}")
.Build());
}
if (issues.Count > 0)
{
return Task.FromResult(result
.Warn($"{issues.Count} configuration issue(s) found")
.WithEvidence("Authority configuration", e =>
{
e.Add("ConfiguredPlugins", string.Join(", ", configuredPlugins));
e.Add("Issues", string.Join("; ", issues));
})
.WithCauses(issues.ToArray())
.WithRemediation(r => r
.AddManualStep(1, "Review configuration", "Check Authority:Plugins section for missing values")
.AddStep(2, "Run setup wizard", "stella setup --step authority", CommandType.Shell))
.WithVerification($"stella doctor --check {CheckId}")
.Build());
}
return Task.FromResult(result
.Pass($"{configuredPlugins.Count} authentication plugin(s) configured")
.WithEvidence("Authority configuration", e =>
{
e.Add("ConfiguredPlugins", string.Join(", ", configuredPlugins));
e.Add("PrimaryPlugin", configuredPlugins[0]);
})
.Build());
}
}

View File

@@ -0,0 +1,163 @@
using Microsoft.Extensions.Configuration;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
namespace StellaOps.Doctor.Plugins.Authority.Checks;
/// <summary>
/// Validates connectivity to configured authentication backends (DB for Standard, LDAP server for LDAP).
/// </summary>
public sealed class AuthorityPluginConnectivityCheck : IDoctorCheck
{
/// <inheritdoc />
public string CheckId => "check.authority.plugin.connectivity";
/// <inheritdoc />
public string Name => "Authority Backend Connectivity";
/// <inheritdoc />
public string Description => "Tests connectivity to authentication backends (database or LDAP server)";
/// <inheritdoc />
public DoctorSeverity DefaultSeverity => DoctorSeverity.Fail;
/// <inheritdoc />
public IReadOnlyList<string> Tags => ["authority", "connectivity", "ldap", "database"];
/// <inheritdoc />
public TimeSpan EstimatedDuration => TimeSpan.FromSeconds(5);
/// <inheritdoc />
public bool CanRun(DoctorPluginContext context)
{
// Check if any plugin is configured
var standardSection = context.Configuration.GetSection("Authority:Plugins:Standard");
var ldapSection = context.Configuration.GetSection("Authority:Plugins:Ldap");
return standardSection.Exists() || ldapSection.Exists();
}
/// <inheritdoc />
public async Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
{
var result = context.CreateResult(CheckId, "stellaops.doctor.authority", DoctorCategory.Authority.ToString());
var connectivityResults = new List<(string Backend, bool Connected, string? Error)>();
// Check Standard plugin (database) connectivity
var standardEnabled = context.Configuration.GetValue<bool?>("Authority:Plugins:Standard:Enabled");
if (standardEnabled == true)
{
var dbConnected = await TestDatabaseConnectivityAsync(context, ct);
connectivityResults.Add(("Database (Standard)", dbConnected.Success, dbConnected.Error));
}
// Check LDAP plugin connectivity
var ldapEnabled = context.Configuration.GetValue<bool?>("Authority:Plugins:Ldap:Enabled");
if (ldapEnabled == true)
{
var ldapServer = context.Configuration.GetValue<string>("Authority:Plugins:Ldap:Server");
if (!string.IsNullOrWhiteSpace(ldapServer))
{
var ldapConnected = await TestLdapConnectivityAsync(ldapServer, context, ct);
connectivityResults.Add(("LDAP Server", ldapConnected.Success, ldapConnected.Error));
}
}
if (connectivityResults.Count == 0)
{
return result
.Skip("No authentication backends configured to test")
.Build();
}
var failedBackends = connectivityResults.Where(r => !r.Connected).ToList();
if (failedBackends.Count > 0)
{
var evidenceBuilder = result.Fail($"{failedBackends.Count} backend(s) unreachable");
return evidenceBuilder
.WithEvidence("Connectivity results", e =>
{
foreach (var (backend, connected, error) in connectivityResults)
{
e.Add(backend, connected ? "Connected" : $"Failed: {error}");
}
})
.WithCauses(failedBackends.Select(f => $"{f.Backend}: {f.Error}").ToArray())
.WithRemediation(r =>
{
if (failedBackends.Any(f => f.Backend.Contains("Database")))
{
r.AddManualStep(1, "Check database", "Verify PostgreSQL is running and accessible");
r.AddStep(2, "Test database connection", "stella doctor --check check.database.connectivity", CommandType.Shell);
}
if (failedBackends.Any(f => f.Backend.Contains("LDAP")))
{
r.AddManualStep(3, "Check LDAP server", "Verify LDAP server is accessible from this network");
r.AddManualStep(4, "Verify LDAP credentials", "Check Authority:Plugins:Ldap:BindDn and BindPassword");
}
})
.WithVerification($"stella doctor --check {CheckId}")
.Build();
}
return result
.Pass($"All {connectivityResults.Count} backend(s) reachable")
.WithEvidence("Connectivity results", e =>
{
foreach (var (backend, connected, _) in connectivityResults)
{
e.Add(backend, "Connected");
}
})
.Build();
}
private static Task<(bool Success, string? Error)> TestDatabaseConnectivityAsync(
DoctorPluginContext context,
CancellationToken ct)
{
// In a real implementation, this would test the database connection.
// For now, we assume success if the connection string is configured.
var connectionString = context.Configuration.GetConnectionString("Authority")
?? context.Configuration.GetConnectionString("Default");
if (string.IsNullOrWhiteSpace(connectionString))
{
return Task.FromResult<(bool Success, string? Error)>((false, "No connection string configured"));
}
// TODO: Actually test the connection when integrated with Authority services
return Task.FromResult((true, (string?)null));
}
private static async Task<(bool Success, string? Error)> TestLdapConnectivityAsync(
string ldapServer,
DoctorPluginContext context,
CancellationToken ct)
{
try
{
// Parse LDAP URI to get host and port
var uri = new Uri(ldapServer);
var host = uri.Host;
var port = uri.Port > 0 ? uri.Port : (uri.Scheme == "ldaps" ? 636 : 389);
using var client = new System.Net.Sockets.TcpClient();
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
cts.CancelAfter(TimeSpan.FromSeconds(5));
await client.ConnectAsync(host, port, cts.Token);
return (true, null);
}
catch (OperationCanceledException)
{
return (false, "Connection timed out");
}
catch (Exception ex)
{
return (false, ex.Message);
}
}
}

View File

@@ -0,0 +1,141 @@
using Microsoft.Extensions.Configuration;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
namespace StellaOps.Doctor.Plugins.Authority.Checks;
/// <summary>
/// Validates that a bootstrap/super user exists in the system.
/// </summary>
public sealed class BootstrapUserExistsCheck : IDoctorCheck
{
/// <inheritdoc />
public string CheckId => "check.authority.bootstrap.exists";
/// <inheritdoc />
public string Name => "Bootstrap User Exists";
/// <inheritdoc />
public string Description => "Verifies that at least one bootstrap/admin user exists";
/// <inheritdoc />
public DoctorSeverity DefaultSeverity => DoctorSeverity.Fail;
/// <inheritdoc />
public IReadOnlyList<string> Tags => ["authority", "user", "bootstrap", "admin", "security"];
/// <inheritdoc />
public TimeSpan EstimatedDuration => TimeSpan.FromMilliseconds(500);
/// <inheritdoc />
public bool CanRun(DoctorPluginContext context)
{
// Only run if Standard plugin is enabled (LDAP users are managed externally)
var standardEnabled = context.Configuration.GetValue<bool?>("Authority:Plugins:Standard:Enabled");
return standardEnabled == true;
}
/// <inheritdoc />
public Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
{
var result = context.CreateResult(CheckId, "stellaops.doctor.authority", DoctorCategory.Authority.ToString());
// Check if bootstrap user configuration exists
var bootstrapUsername = context.Configuration.GetValue<string>("Authority:Bootstrap:Username")
?? context.Configuration.GetValue<string>("Authority:Plugins:Standard:Bootstrap:Username");
var bootstrapEmail = context.Configuration.GetValue<string>("Authority:Bootstrap:Email")
?? context.Configuration.GetValue<string>("Authority:Plugins:Standard:Bootstrap:Email");
// Check if auto-bootstrap is configured
var autoBootstrap = context.Configuration.GetValue<bool?>("Authority:Bootstrap:Enabled")
?? context.Configuration.GetValue<bool?>("Authority:Plugins:Standard:Bootstrap:Enabled")
?? true;
var hasBootstrapConfig = !string.IsNullOrWhiteSpace(bootstrapUsername)
|| !string.IsNullOrWhiteSpace(bootstrapEmail);
if (!autoBootstrap && !hasBootstrapConfig)
{
return Task.FromResult(result
.Fail("No bootstrap user configured and auto-bootstrap is disabled")
.WithEvidence("Bootstrap configuration", e =>
{
e.Add("AutoBootstrap", "false");
e.Add("BootstrapUsername", "(not set)");
e.Add("BootstrapEmail", "(not set)");
})
.WithCauses(
"Authority:Bootstrap:Enabled is false",
"No bootstrap user credentials configured",
"System cannot create initial admin user")
.WithRemediation(r => r
.AddStep(1, "Enable auto-bootstrap",
"# Add to appsettings.json:\n" +
"\"Authority\": {\n" +
" \"Bootstrap\": {\n" +
" \"Enabled\": true,\n" +
" \"Username\": \"admin\",\n" +
" \"Email\": \"admin@example.com\"\n" +
" }\n" +
"}",
CommandType.FileEdit)
.AddStep(2, "Or run setup wizard to create user",
"stella setup --step users",
CommandType.Shell))
.WithVerification($"stella doctor --check {CheckId}")
.Build());
}
if (!hasBootstrapConfig)
{
return Task.FromResult(result
.Info("Bootstrap user will be auto-created on first startup")
.WithEvidence("Bootstrap configuration", e =>
{
e.Add("AutoBootstrap", "true");
e.Add("Status", "Will be created on startup");
e.Add("Note", "Default admin user will be created if no users exist");
})
.Build());
}
// Validate bootstrap configuration completeness
var issues = new List<string>();
if (string.IsNullOrWhiteSpace(bootstrapUsername))
{
issues.Add("Bootstrap username not set");
}
if (string.IsNullOrWhiteSpace(bootstrapEmail))
{
issues.Add("Bootstrap email not set");
}
if (issues.Count > 0)
{
return Task.FromResult(result
.Warn("Bootstrap user configuration is incomplete")
.WithEvidence("Bootstrap configuration", e =>
{
e.Add("Username", bootstrapUsername ?? "(not set)");
e.Add("Email", bootstrapEmail ?? "(not set)");
e.Add("AutoBootstrap", autoBootstrap.ToString());
})
.WithCauses(issues.ToArray())
.WithRemediation(r => r
.AddManualStep(1, "Complete configuration", "Set missing bootstrap user fields")
.AddStep(2, "Run setup wizard", "stella setup --step users", CommandType.Shell))
.WithVerification($"stella doctor --check {CheckId}")
.Build());
}
return Task.FromResult(result
.Pass("Bootstrap user is properly configured")
.WithEvidence("Bootstrap configuration", e =>
{
e.Add("Username", bootstrapUsername!);
e.Add("Email", bootstrapEmail!);
e.Add("AutoBootstrap", autoBootstrap.ToString());
})
.Build());
}
}

View File

@@ -0,0 +1,126 @@
using Microsoft.Extensions.Configuration;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
namespace StellaOps.Doctor.Plugins.Authority.Checks;
/// <summary>
/// Validates that at least one super user (administrator) exists in the system.
/// </summary>
public sealed class SuperUserExistsCheck : IDoctorCheck
{
/// <inheritdoc />
public string CheckId => "check.users.superuser.exists";
/// <inheritdoc />
public string Name => "Super User Exists";
/// <inheritdoc />
public string Description => "Verifies that at least one administrator user exists";
/// <inheritdoc />
public DoctorSeverity DefaultSeverity => DoctorSeverity.Fail;
/// <inheritdoc />
public IReadOnlyList<string> Tags => ["authority", "user", "admin", "superuser", "security"];
/// <inheritdoc />
public TimeSpan EstimatedDuration => TimeSpan.FromSeconds(1);
/// <inheritdoc />
public bool CanRun(DoctorPluginContext context)
{
// Only run if Standard plugin is enabled
var standardEnabled = context.Configuration.GetValue<bool?>("Authority:Plugins:Standard:Enabled");
return standardEnabled == true;
}
/// <inheritdoc />
public Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
{
var result = context.CreateResult(CheckId, "stellaops.doctor.authority", DoctorCategory.Authority.ToString());
// Check for configured super users
var superUsersSection = context.Configuration.GetSection("Authority:Users:Administrators");
var bootstrapUsername = context.Configuration.GetValue<string>("Authority:Bootstrap:Username");
var configuredAdmins = new List<string>();
if (!string.IsNullOrWhiteSpace(bootstrapUsername))
{
configuredAdmins.Add(bootstrapUsername);
}
// Check if there are explicitly configured administrators
if (superUsersSection.Exists())
{
var admins = superUsersSection.GetChildren().Select(c => c.Value).Where(v => !string.IsNullOrWhiteSpace(v)).ToList();
configuredAdmins.AddRange(admins!);
}
// Check for admin role assignments
var adminRoles = context.Configuration.GetSection("Authority:Roles:Administrators");
if (adminRoles.Exists())
{
var roleMembers = adminRoles.GetChildren().Select(c => c.Value).Where(v => !string.IsNullOrWhiteSpace(v)).ToList();
configuredAdmins.AddRange(roleMembers!);
}
configuredAdmins = configuredAdmins.Distinct().ToList();
if (configuredAdmins.Count == 0)
{
// Check if auto-bootstrap will create one
var autoBootstrap = context.Configuration.GetValue<bool?>("Authority:Bootstrap:Enabled") ?? true;
if (autoBootstrap)
{
return Task.FromResult(result
.Info("No administrators configured but auto-bootstrap is enabled")
.WithEvidence("Administrator status", e =>
{
e.Add("ConfiguredAdmins", "(none)");
e.Add("AutoBootstrap", "true");
e.Add("Note", "Bootstrap user will be created as administrator on first startup");
})
.Build());
}
return Task.FromResult(result
.Fail("No administrator users configured")
.WithEvidence("Administrator status", e =>
{
e.Add("ConfiguredAdmins", "(none)");
e.Add("AutoBootstrap", "false");
})
.WithCauses(
"No users assigned to administrator role",
"Bootstrap user not configured",
"Auto-bootstrap is disabled")
.WithRemediation(r => r
.AddStep(1, "Create super user via setup wizard",
"stella setup --step users",
CommandType.Shell)
.AddStep(2, "Or enable auto-bootstrap",
"# Add to appsettings.json:\n" +
"\"Authority\": {\n" +
" \"Bootstrap\": { \"Enabled\": true }\n" +
"}",
CommandType.FileEdit))
.WithVerification($"stella doctor --check {CheckId}")
.Build());
}
return Task.FromResult(result
.Pass($"{configuredAdmins.Count} administrator(s) configured")
.WithEvidence("Administrator status", e =>
{
e.Add("ConfiguredAdmins", string.Join(", ", configuredAdmins.Take(5)));
if (configuredAdmins.Count > 5)
{
e.Add("AdditionalAdmins", $"+{configuredAdmins.Count - 5} more");
}
})
.Build());
}
}

View File

@@ -0,0 +1,153 @@
using Microsoft.Extensions.Configuration;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
namespace StellaOps.Doctor.Plugins.Authority.Checks;
/// <summary>
/// Validates that password policy settings meet security requirements.
/// </summary>
public sealed class UserPasswordPolicyCheck : IDoctorCheck
{
private const int RecommendedMinLength = 12;
private const int MinimumMinLength = 8;
/// <inheritdoc />
public string CheckId => "check.users.password.policy";
/// <inheritdoc />
public string Name => "Password Policy";
/// <inheritdoc />
public string Description => "Validates password policy configuration meets security requirements";
/// <inheritdoc />
public DoctorSeverity DefaultSeverity => DoctorSeverity.Warn;
/// <inheritdoc />
public IReadOnlyList<string> Tags => ["authority", "password", "policy", "security"];
/// <inheritdoc />
public TimeSpan EstimatedDuration => TimeSpan.FromMilliseconds(50);
/// <inheritdoc />
public bool CanRun(DoctorPluginContext context)
{
// Only run if Standard plugin is enabled (LDAP manages passwords externally)
var standardEnabled = context.Configuration.GetValue<bool?>("Authority:Plugins:Standard:Enabled");
return standardEnabled == true;
}
/// <inheritdoc />
public Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
{
var result = context.CreateResult(CheckId, "stellaops.doctor.authority", DoctorCategory.Authority.ToString());
// Get password policy settings with defaults
var policySection = context.Configuration.GetSection("Authority:PasswordPolicy");
var minLength = context.Configuration.GetValue<int?>("Authority:PasswordPolicy:MinLength") ?? 8;
var requireUppercase = context.Configuration.GetValue<bool?>("Authority:PasswordPolicy:RequireUppercase") ?? true;
var requireLowercase = context.Configuration.GetValue<bool?>("Authority:PasswordPolicy:RequireLowercase") ?? true;
var requireDigit = context.Configuration.GetValue<bool?>("Authority:PasswordPolicy:RequireDigit") ?? true;
var requireSpecialChar = context.Configuration.GetValue<bool?>("Authority:PasswordPolicy:RequireSpecialCharacter") ?? true;
var maxAge = context.Configuration.GetValue<int?>("Authority:PasswordPolicy:MaxAgeDays");
var preventReuse = context.Configuration.GetValue<int?>("Authority:PasswordPolicy:PreventReuseCount") ?? 5;
var issues = new List<string>();
var recommendations = new List<string>();
// Check minimum length
if (minLength < MinimumMinLength)
{
issues.Add($"Minimum password length ({minLength}) is below absolute minimum ({MinimumMinLength})");
}
else if (minLength < RecommendedMinLength)
{
recommendations.Add($"Consider increasing minimum length from {minLength} to {RecommendedMinLength}");
}
// Check complexity requirements
var enabledRequirements = new List<string>();
if (requireUppercase) enabledRequirements.Add("Uppercase");
if (requireLowercase) enabledRequirements.Add("Lowercase");
if (requireDigit) enabledRequirements.Add("Digit");
if (requireSpecialChar) enabledRequirements.Add("Special character");
if (enabledRequirements.Count < 3)
{
recommendations.Add($"Only {enabledRequirements.Count} complexity requirements enabled (recommend 3+)");
}
// Check password age policy
if (maxAge.HasValue && maxAge.Value < 30)
{
recommendations.Add($"Password max age ({maxAge} days) is very short - may frustrate users");
}
// Check reuse prevention
if (preventReuse < 3)
{
recommendations.Add($"Password reuse prevention ({preventReuse}) is low (recommend 5+)");
}
if (issues.Count > 0)
{
return Task.FromResult(result
.Fail($"{issues.Count} password policy violation(s)")
.WithEvidence("Password policy", e =>
{
e.Add("MinLength", minLength.ToString());
e.Add("Requirements", string.Join(", ", enabledRequirements));
e.Add("MaxAgeDays", maxAge?.ToString() ?? "(not set)");
e.Add("PreventReuseCount", preventReuse.ToString());
})
.WithCauses(issues.ToArray())
.WithRemediation(r => r
.AddStep(1, "Update password policy",
"# Add to appsettings.json:\n" +
"\"Authority\": {\n" +
" \"PasswordPolicy\": {\n" +
" \"MinLength\": 12,\n" +
" \"RequireUppercase\": true,\n" +
" \"RequireLowercase\": true,\n" +
" \"RequireDigit\": true,\n" +
" \"RequireSpecialCharacter\": true\n" +
" }\n" +
"}",
CommandType.FileEdit))
.WithVerification($"stella doctor --check {CheckId}")
.Build());
}
if (recommendations.Count > 0)
{
return Task.FromResult(result
.Warn($"{recommendations.Count} password policy recommendation(s)")
.WithEvidence("Password policy", e =>
{
e.Add("MinLength", minLength.ToString());
e.Add("Requirements", string.Join(", ", enabledRequirements));
e.Add("MaxAgeDays", maxAge?.ToString() ?? "(not set)");
e.Add("PreventReuseCount", preventReuse.ToString());
})
.WithCauses(recommendations.ToArray())
.WithRemediation(r => r
.AddManualStep(1, "Review recommendations", "Consider strengthening password policy")
.AddStep(2, "Run setup wizard", "stella setup --step authority", CommandType.Shell))
.WithVerification($"stella doctor --check {CheckId}")
.Build());
}
return Task.FromResult(result
.Pass("Password policy meets security requirements")
.WithEvidence("Password policy", e =>
{
e.Add("MinLength", minLength.ToString());
e.Add("Requirements", string.Join(", ", enabledRequirements));
e.Add("MaxAgeDays", maxAge?.ToString() ?? "(not enforced)");
e.Add("PreventReuseCount", preventReuse.ToString());
})
.Build());
}
}