Doctor plugin checks: implement health check classes and documentation
Implement remediation-aware health checks across all Doctor plugin modules (Agent, Attestor, Auth, BinaryAnalysis, Compliance, Crypto, Environment, EvidenceLocker, Notify, Observability, Operations, Policy, Postgres, Release, Scanner, Storage, Vex) and their backing library counterparts (AI, Attestation, Authority, Core, Cryptography, Database, Docker, Integration, Notify, Observability, Security, ServiceGraph, Sources, Verification). Each check now emits structured remediation metadata (severity, category, runbook links, and fix suggestions) consumed by the Doctor dashboard remediation panel. Also adds: - docs/doctor/articles/ knowledge base for check explanations - Advisory AI search seed and allowlist updates for doctor content - Sprint plan for doctor checks documentation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -155,7 +155,8 @@ public sealed class ClaudeProviderCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Verify API key", "Check ANTHROPIC_API_KEY is valid")
|
||||
.AddManualStep(2, "Check quotas", "Verify API usage limits on console.anthropic.com"))
|
||||
.AddManualStep(2, "Check quotas", "Verify API usage limits on console.anthropic.com")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.ai.provider.claude")
|
||||
.Build();
|
||||
}
|
||||
@@ -171,7 +172,8 @@ public sealed class ClaudeProviderCheck : IDoctorCheck
|
||||
.WithCauses("Network connectivity issue or invalid endpoint")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check network", "Verify network connectivity to api.anthropic.com")
|
||||
.AddManualStep(2, "Check proxy", "Ensure proxy settings are configured if required"))
|
||||
.AddManualStep(2, "Check proxy", "Ensure proxy settings are configured if required")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.ai.provider.claude")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -143,7 +143,8 @@ public sealed class GeminiProviderCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Verify API key", "Check GEMINI_API_KEY or GOOGLE_API_KEY is valid")
|
||||
.AddManualStep(2, "Enable API", "Ensure Generative Language API is enabled in Google Cloud Console")
|
||||
.AddManualStep(3, "Check quotas", "Verify API usage limits in Google Cloud Console"))
|
||||
.AddManualStep(3, "Check quotas", "Verify API usage limits in Google Cloud Console")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.ai.provider.gemini")
|
||||
.Build();
|
||||
}
|
||||
@@ -159,7 +160,8 @@ public sealed class GeminiProviderCheck : IDoctorCheck
|
||||
.WithCauses("Network connectivity issue or invalid endpoint")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check network", "Verify network connectivity to generativelanguage.googleapis.com")
|
||||
.AddManualStep(2, "Check proxy", "Ensure proxy settings are configured if required"))
|
||||
.AddManualStep(2, "Check proxy", "Ensure proxy settings are configured if required")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.ai.provider.gemini")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -143,7 +143,8 @@ public sealed class LlmProviderConfigurationCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Set API key", "Configure API key for the default provider")
|
||||
.AddManualStep(2, "Verify provider", "Ensure default provider matches a configured one"))
|
||||
.AddManualStep(2, "Verify provider", "Ensure default provider matches a configured one")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.ai.llm.config")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -142,7 +142,8 @@ public sealed class LocalInferenceCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Load model", "Ensure a model is loaded in the server")
|
||||
.AddManualStep(2, "Check model path", "Verify the model file exists at configured path"))
|
||||
.AddManualStep(2, "Check model path", "Verify the model file exists at configured path")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.ai.provider.local")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -84,7 +84,8 @@ public sealed class OllamaProviderCheck : IDoctorCheck
|
||||
.WithCauses("Ollama server is not running or endpoint is incorrect")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Start Ollama", "Run: ollama serve")
|
||||
.AddManualStep(2, "Check endpoint", $"Verify Ollama is running at {endpoint}"))
|
||||
.AddManualStep(2, "Check endpoint", $"Verify Ollama is running at {endpoint}")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.ai.provider.ollama")
|
||||
.Build();
|
||||
}
|
||||
@@ -160,7 +161,8 @@ public sealed class OllamaProviderCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Pull model", $"Run: ollama pull {model}")
|
||||
.AddManualStep(2, "List models", "Run: ollama list"))
|
||||
.AddManualStep(2, "List models", "Run: ollama list")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.ai.provider.ollama")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -139,7 +139,8 @@ public sealed class OpenAiProviderCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Verify API key", "Check OPENAI_API_KEY is valid")
|
||||
.AddManualStep(2, "Check quotas", "Verify API usage limits on platform.openai.com"))
|
||||
.AddManualStep(2, "Check quotas", "Verify API usage limits on platform.openai.com")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.ai.provider.openai")
|
||||
.Build();
|
||||
}
|
||||
@@ -155,7 +156,8 @@ public sealed class OpenAiProviderCheck : IDoctorCheck
|
||||
.WithCauses("Network connectivity issue or invalid endpoint")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check network", "Verify network connectivity to api.openai.com")
|
||||
.AddManualStep(2, "Check proxy", "Ensure proxy settings are configured if required"))
|
||||
.AddManualStep(2, "Check proxy", "Ensure proxy settings are configured if required")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.ai.provider.openai")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -96,7 +96,8 @@ public sealed class ClockSkewCheck : AttestationCheckBase
|
||||
.Add("Note", "Clock skew verification skipped - no network reference available"))
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Check system time", GetTimeCheckCommand())
|
||||
.AddManualStep(2, "Configure NTP", "Ensure NTP is configured for time synchronization"))
|
||||
.AddManualStep(2, "Configure NTP", "Ensure NTP is configured for time synchronization")
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-clock-skew.md"))
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -122,7 +123,8 @@ public sealed class ClockSkewCheck : AttestationCheckBase
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Check current time", GetTimeCheckCommand())
|
||||
.AddShellStep(2, "Force NTP sync", GetNtpSyncCommand())
|
||||
.AddManualStep(3, "Configure NTP", "Ensure NTP is properly configured and the NTP service is running"))
|
||||
.AddManualStep(3, "Configure NTP", "Ensure NTP is properly configured and the NTP service is running")
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-clock-skew.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build();
|
||||
}
|
||||
@@ -143,7 +145,8 @@ public sealed class ClockSkewCheck : AttestationCheckBase
|
||||
"Infrequent NTP sync interval")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Check NTP status", GetNtpStatusCommand())
|
||||
.AddShellStep(2, "Force NTP sync", GetNtpSyncCommand()))
|
||||
.AddShellStep(2, "Force NTP sync", GetNtpSyncCommand())
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-clock-skew.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -93,7 +93,8 @@ public sealed class CosignKeyMaterialCheck : AttestationCheckBase
|
||||
.Add("Note", "Enable Sigstore to use attestation signing"))
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Enable Sigstore", "Set Sigstore:Enabled to true in configuration")
|
||||
.AddManualStep(2, "Configure signing mode", "Set either Sigstore:KeyPath, Sigstore:Keyless:Enabled, or Sigstore:KMS:KeyRef"))
|
||||
.AddManualStep(2, "Configure signing mode", "Set either Sigstore:KeyPath, Sigstore:Keyless:Enabled, or Sigstore:KMS:KeyRef")
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-cosign-keymaterial.md"))
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -112,7 +113,8 @@ public sealed class CosignKeyMaterialCheck : AttestationCheckBase
|
||||
.AddShellStep(1, "Generate a signing key pair", "cosign generate-key-pair")
|
||||
.AddManualStep(2, "Configure key path", "Set Sigstore:KeyPath to the path of the private key")
|
||||
.AddManualStep(3, "Or enable keyless", "Set Sigstore:Keyless:Enabled to true for OIDC-based signing")
|
||||
.AddManualStep(4, "Or use KMS", "Set Sigstore:KMS:KeyRef to your KMS key reference"))
|
||||
.AddManualStep(4, "Or use KMS", "Set Sigstore:KMS:KeyRef to your KMS key reference")
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-cosign-keymaterial.md"))
|
||||
.WithVerification($"stella doctor --check check.attestation.cosign.keymaterial")
|
||||
.Build());
|
||||
}
|
||||
@@ -135,7 +137,8 @@ public sealed class CosignKeyMaterialCheck : AttestationCheckBase
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Verify file exists", $"ls -la {keyPath}")
|
||||
.AddShellStep(2, "Generate new key pair if needed", "cosign generate-key-pair")
|
||||
.AddManualStep(3, "Update configuration", "Ensure Sigstore:KeyPath points to the correct file"))
|
||||
.AddManualStep(3, "Update configuration", "Ensure Sigstore:KeyPath points to the correct file")
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-cosign-keymaterial.md"))
|
||||
.WithVerification($"stella doctor --check check.attestation.cosign.keymaterial")
|
||||
.Build());
|
||||
}
|
||||
@@ -172,7 +175,8 @@ public sealed class CosignKeyMaterialCheck : AttestationCheckBase
|
||||
.WithCauses("File permissions prevent reading the key file")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Check file permissions", $"ls -la {keyPath}")
|
||||
.AddShellStep(2, "Fix permissions if needed", $"chmod 600 {keyPath}"))
|
||||
.AddShellStep(2, "Fix permissions if needed", $"chmod 600 {keyPath}")
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-cosign-keymaterial.md"))
|
||||
.WithVerification($"stella doctor --check check.attestation.cosign.keymaterial")
|
||||
.Build());
|
||||
}
|
||||
@@ -213,7 +217,8 @@ public sealed class CosignKeyMaterialCheck : AttestationCheckBase
|
||||
"Fulcio URL is incorrect")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Test Fulcio endpoint", $"curl -I {fulcioApiUrl}")
|
||||
.AddManualStep(2, "Check service status", "Visit https://status.sigstore.dev"))
|
||||
.AddManualStep(2, "Check service status", "Visit https://status.sigstore.dev")
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-cosign-keymaterial.md"))
|
||||
.WithVerification($"stella doctor --check check.attestation.cosign.keymaterial")
|
||||
.Build();
|
||||
}
|
||||
@@ -242,7 +247,8 @@ public sealed class CosignKeyMaterialCheck : AttestationCheckBase
|
||||
"Firewall blocking HTTPS traffic")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Test connectivity", $"curl -I {fulcioUrl}")
|
||||
.AddManualStep(2, "Check network configuration", "Ensure HTTPS traffic to Fulcio is allowed"))
|
||||
.AddManualStep(2, "Check network configuration", "Ensure HTTPS traffic to Fulcio is allowed")
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-cosign-keymaterial.md"))
|
||||
.WithVerification($"stella doctor --check check.attestation.cosign.keymaterial")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -69,7 +69,8 @@ public sealed class OfflineBundleCheck : AttestationCheckBase
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Export bundle from online system", "stella attestation bundle export --output /path/to/bundle.json")
|
||||
.AddManualStep(2, "Configure bundle path", "Set Doctor:Plugins:Attestation:OfflineBundlePath to the bundle location")
|
||||
.AddManualStep(3, "Transfer bundle", "Copy the bundle to the target system"))
|
||||
.AddManualStep(3, "Transfer bundle", "Copy the bundle to the target system")
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-offline-bundle.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
@@ -89,7 +90,8 @@ public sealed class OfflineBundleCheck : AttestationCheckBase
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Check file existence", $"ls -la {options.OfflineBundlePath}")
|
||||
.AddShellStep(2, "Export new bundle", "stella attestation bundle export --output " + options.OfflineBundlePath)
|
||||
.AddManualStep(3, "Verify path", "Ensure the configured path is correct"))
|
||||
.AddManualStep(3, "Verify path", "Ensure the configured path is correct")
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-offline-bundle.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
@@ -134,7 +136,8 @@ public sealed class OfflineBundleCheck : AttestationCheckBase
|
||||
.Add("ParseError", parseError))
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Validate bundle", "stella attestation bundle validate " + options.OfflineBundlePath)
|
||||
.AddShellStep(2, "Export fresh bundle", "stella attestation bundle export --output " + options.OfflineBundlePath))
|
||||
.AddShellStep(2, "Export fresh bundle", "stella attestation bundle export --output " + options.OfflineBundlePath)
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-offline-bundle.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
@@ -167,7 +170,8 @@ public sealed class OfflineBundleCheck : AttestationCheckBase
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Export fresh bundle from online system", "stella attestation bundle export --output /path/to/new-bundle.json")
|
||||
.AddManualStep(2, "Transfer to air-gap environment", "Copy the new bundle to the target system")
|
||||
.AddManualStep(3, "Update bundle path if needed", "Point configuration to the new bundle file"))
|
||||
.AddManualStep(3, "Update bundle path if needed", "Point configuration to the new bundle file")
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-offline-bundle.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
@@ -191,7 +195,8 @@ public sealed class OfflineBundleCheck : AttestationCheckBase
|
||||
})
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Export fresh bundle", "stella attestation bundle export --output /path/to/new-bundle.json")
|
||||
.AddManualStep(2, "Schedule regular updates", "Consider automating bundle refresh"))
|
||||
.AddManualStep(2, "Schedule regular updates", "Consider automating bundle refresh")
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-offline-bundle.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -60,7 +60,8 @@ public sealed class RekorConnectivityCheck : AttestationCheckBase
|
||||
.Add("ConfigKey", "Doctor:Plugins:Attestation:RekorUrl or Sigstore:RekorUrl"))
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Configure Rekor URL", "Set the Rekor URL in configuration: STELLA_REKOR_URL=https://rekor.sigstore.dev")
|
||||
.AddManualStep(2, "Or use offline mode", "Set Doctor:Plugins:Attestation:Mode to 'offline' and configure OfflineBundlePath"))
|
||||
.AddManualStep(2, "Or use offline mode", "Set Doctor:Plugins:Attestation:Mode to 'offline' and configure OfflineBundlePath")
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-rekor-connectivity.md"))
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -86,7 +87,8 @@ public sealed class RekorConnectivityCheck : AttestationCheckBase
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Test endpoint manually", $"curl -I {logInfoUrl}")
|
||||
.AddManualStep(2, "Verify Rekor URL", "Ensure the URL is correct (default: https://rekor.sigstore.dev)")
|
||||
.AddManualStep(3, "Check service status", "Visit https://status.sigstore.dev for public Rekor status"))
|
||||
.AddManualStep(3, "Check service status", "Visit https://status.sigstore.dev for public Rekor status")
|
||||
.WithRunbookUrl("docs/doctor/articles/attestor/attestation-rekor-connectivity.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -122,7 +122,8 @@ public sealed class AuthorityPluginConfigurationCheck : IDoctorCheck
|
||||
CommandType.FileEdit)
|
||||
.AddStep(3, "Run setup wizard to configure",
|
||||
"stella setup --step authority",
|
||||
CommandType.Shell))
|
||||
CommandType.Shell)
|
||||
.WithRunbookUrl("docs/doctor/articles/auth/authority-plugin-configured.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
@@ -139,7 +140,8 @@ public sealed class AuthorityPluginConfigurationCheck : IDoctorCheck
|
||||
.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))
|
||||
.AddStep(2, "Run setup wizard", "stella setup --step authority", CommandType.Shell)
|
||||
.WithRunbookUrl("docs/doctor/articles/auth/authority-plugin-configured.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ public sealed class AuthorityPluginConnectivityCheck : IDoctorCheck
|
||||
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");
|
||||
}
|
||||
r.WithRunbookUrl("docs/doctor/articles/auth/authority-plugin-connectivity.md");
|
||||
})
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build();
|
||||
|
||||
@@ -81,7 +81,8 @@ public sealed class BootstrapUserExistsCheck : IDoctorCheck
|
||||
CommandType.FileEdit)
|
||||
.AddStep(2, "Or run setup wizard to create user",
|
||||
"stella setup --step users",
|
||||
CommandType.Shell))
|
||||
CommandType.Shell)
|
||||
.WithRunbookUrl("docs/doctor/articles/auth/authority-bootstrap-exists.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
@@ -123,7 +124,8 @@ public sealed class BootstrapUserExistsCheck : IDoctorCheck
|
||||
.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))
|
||||
.AddStep(2, "Run setup wizard", "stella setup --step users", CommandType.Shell)
|
||||
.WithRunbookUrl("docs/doctor/articles/auth/authority-bootstrap-exists.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -106,7 +106,8 @@ public sealed class SuperUserExistsCheck : IDoctorCheck
|
||||
"\"Authority\": {\n" +
|
||||
" \"Bootstrap\": { \"Enabled\": true }\n" +
|
||||
"}",
|
||||
CommandType.FileEdit))
|
||||
CommandType.FileEdit)
|
||||
.WithRunbookUrl("docs/doctor/articles/auth/users-superuser-exists.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -115,7 +115,8 @@ public sealed class UserPasswordPolicyCheck : IDoctorCheck
|
||||
" \"RequireSpecialCharacter\": true\n" +
|
||||
" }\n" +
|
||||
"}",
|
||||
CommandType.FileEdit))
|
||||
CommandType.FileEdit)
|
||||
.WithRunbookUrl("docs/doctor/articles/auth/users-password-policy.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
@@ -134,7 +135,8 @@ public sealed class UserPasswordPolicyCheck : IDoctorCheck
|
||||
.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))
|
||||
.AddStep(2, "Run setup wizard", "stella setup --step authority", CommandType.Shell)
|
||||
.WithRunbookUrl("docs/doctor/articles/auth/users-password-policy.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -125,7 +125,8 @@ public sealed class AuthenticationConfigCheck : IDoctorCheck
|
||||
.AddManualStep(1, "Review authentication settings",
|
||||
"Check appsettings.json Authentication section for proper configuration")
|
||||
.AddManualStep(2, "Use strong secrets",
|
||||
"Ensure JWT secrets are at least 32 characters and not default values"))
|
||||
"Ensure JWT secrets are at least 32 characters and not default values")
|
||||
.WithRunbookUrl("docs/doctor/articles/core/core-auth-config.md"))
|
||||
.WithVerification("stella doctor --check check.core.auth.config")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -56,7 +56,8 @@ public sealed class ConfigurationLoadedCheck : IDoctorCheck
|
||||
"Environment variables not set")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check for configuration files", "Verify appsettings.json or environment-specific config files exist")
|
||||
.AddShellStep(2, "List environment variables", "printenv | grep -i stella"))
|
||||
.AddShellStep(2, "List environment variables", "printenv | grep -i stella")
|
||||
.WithRunbookUrl("docs/doctor/articles/core/core-config-loaded.md"))
|
||||
.WithVerification("stella doctor --check check.core.config.loaded")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -91,7 +91,8 @@ public sealed class CryptoProvidersCheck : IDoctorCheck
|
||||
.AddManualStep(1, "Verify OS crypto support",
|
||||
"Ensure operating system has required cryptographic providers installed")
|
||||
.AddManualStep(2, "Check FIPS compliance requirements",
|
||||
"If FIPS mode is enabled, ensure only FIPS-compliant algorithms are used"))
|
||||
"If FIPS mode is enabled, ensure only FIPS-compliant algorithms are used")
|
||||
.WithRunbookUrl("docs/doctor/articles/core/core-crypto-available.md"))
|
||||
.WithVerification("stella doctor --check check.core.crypto.available")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -84,7 +84,8 @@ public sealed class DependencyServicesCheck : IDoctorCheck
|
||||
"Incorrect service registration order")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Register missing services",
|
||||
$"Add registration for: {string.Join(", ", missing)} in Program.cs or Startup.cs"))
|
||||
$"Add registration for: {string.Join(", ", missing)} in Program.cs or Startup.cs")
|
||||
.WithRunbookUrl("docs/doctor/articles/core/core-services-dependencies.md"))
|
||||
.WithVerification("stella doctor --check check.core.services.dependencies")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@ public sealed class DiskSpaceCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Check large files", "du -sh /* | sort -hr | head -20")
|
||||
.AddShellStep(2, "Clean temp files", "rm -rf /tmp/* 2>/dev/null")
|
||||
.AddShellStep(3, "Rotate logs", "logrotate -f /etc/logrotate.conf"))
|
||||
.AddShellStep(3, "Rotate logs", "logrotate -f /etc/logrotate.conf")
|
||||
.WithRunbookUrl("docs/doctor/articles/core/core-env-diskspace.md"))
|
||||
.WithVerification("stella doctor --check check.core.env.diskspace")
|
||||
.Build());
|
||||
}
|
||||
@@ -91,7 +92,8 @@ public sealed class DiskSpaceCheck : IDoctorCheck
|
||||
.Add("UsedPercent", $"{usedPercent:F1}%"))
|
||||
.WithCauses("Disk usage approaching capacity")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Review disk usage", "Consider archiving or deleting old data"))
|
||||
.AddManualStep(1, "Review disk usage", "Consider archiving or deleting old data")
|
||||
.WithRunbookUrl("docs/doctor/articles/core/core-env-diskspace.md"))
|
||||
.Build());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using StellaOps.Doctor.Plugins.Builders;
|
||||
@@ -8,6 +7,9 @@ namespace StellaOps.Doctor.Plugins.Core.Checks;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that expected environment variables are set.
|
||||
/// In Docker compose and Kubernetes deployments, ASPNETCORE_ENVIRONMENT may
|
||||
/// not be explicitly set (defaults to Production), but other STELLAOPS_*
|
||||
/// variables confirm the environment is intentionally configured.
|
||||
/// </summary>
|
||||
public sealed class EnvironmentVariablesCheck : IDoctorCheck
|
||||
{
|
||||
@@ -50,39 +52,55 @@ public sealed class EnvironmentVariablesCheck : IDoctorCheck
|
||||
{
|
||||
var value = Environment.GetEnvironmentVariable(varName);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
missing.Add(varName);
|
||||
}
|
||||
else
|
||||
{
|
||||
found[varName] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Count total stella-related env vars
|
||||
// Count total stella-related env vars (broader than just the two recommended)
|
||||
var stellaVars = Environment.GetEnvironmentVariables()
|
||||
.Keys.Cast<string>()
|
||||
.Where(k => k.StartsWith("STELLA", StringComparison.OrdinalIgnoreCase) ||
|
||||
k.StartsWith("ASPNETCORE", StringComparison.OrdinalIgnoreCase) ||
|
||||
k.StartsWith("DOTNET", StringComparison.OrdinalIgnoreCase))
|
||||
k.StartsWith("DOTNET", StringComparison.OrdinalIgnoreCase) ||
|
||||
k.StartsWith("CONNECTIONSTRINGS", StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
if (missing.Count > 0 && missing.Count == RecommendedVariables.Length)
|
||||
// If neither recommended var is set but other platform vars exist,
|
||||
// the environment IS configured (e.g. Docker compose with ASPNETCORE_URLS,
|
||||
// STELLAOPS_* vars, ConnectionStrings__*, etc.). Report as pass with note.
|
||||
if (missing.Count == RecommendedVariables.Length)
|
||||
{
|
||||
if (stellaVars.Count > 0)
|
||||
{
|
||||
// Platform is configured via other env vars — pass with advisory
|
||||
return Task.FromResult(result
|
||||
.Pass($"Environment configured ({stellaVars.Count} platform variables detected, using default environment: {context.EnvironmentName})")
|
||||
.WithEvidence("Environment status", e =>
|
||||
{
|
||||
e.Add("CurrentEnvironment", context.EnvironmentName);
|
||||
e.Add("TotalPlatformVars", stellaVars.Count.ToString(CultureInfo.InvariantCulture));
|
||||
e.Add("Note", "ASPNETCORE_ENVIRONMENT not explicitly set — using default. Set it to suppress this note.");
|
||||
})
|
||||
.Build());
|
||||
}
|
||||
|
||||
// No platform vars at all — genuine warning
|
||||
return Task.FromResult(result
|
||||
.Warn("No environment configuration variables detected")
|
||||
.WithEvidence("Environment status", e =>
|
||||
{
|
||||
e.Add("MissingRecommended", string.Join(", ", missing));
|
||||
e.Add("TotalStellaVars", stellaVars.Count.ToString(CultureInfo.InvariantCulture));
|
||||
e.Add("TotalPlatformVars", "0");
|
||||
e.Add("CurrentEnvironment", context.EnvironmentName);
|
||||
})
|
||||
.WithCauses(
|
||||
"Environment variables not set for deployment",
|
||||
"Using default environment (Production)")
|
||||
"No StellaOps, ASP.NET, or .NET environment variables found",
|
||||
"The service may not be running in a configured deployment")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Set environment", "export ASPNETCORE_ENVIRONMENT=Development")
|
||||
.AddManualStep(2, "Configure in deployment", "Set ASPNETCORE_ENVIRONMENT in your deployment configuration"))
|
||||
.AddManualStep(2, "Configure in deployment", "Set ASPNETCORE_ENVIRONMENT in docker-compose.yml or Kubernetes manifest")
|
||||
.WithRunbookUrl("docs/doctor/articles/core/core-env-variables.md"))
|
||||
.WithVerification("stella doctor --check check.core.env.variables")
|
||||
.Build());
|
||||
}
|
||||
@@ -92,10 +110,8 @@ public sealed class EnvironmentVariablesCheck : IDoctorCheck
|
||||
.WithEvidence("Environment status", e =>
|
||||
{
|
||||
foreach (var kv in found)
|
||||
{
|
||||
e.Add(kv.Key, kv.Value);
|
||||
}
|
||||
e.Add("TotalStellaVars", stellaVars.Count.ToString(CultureInfo.InvariantCulture));
|
||||
e.Add("TotalPlatformVars", stellaVars.Count.ToString(CultureInfo.InvariantCulture));
|
||||
})
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ public sealed class MemoryUsageCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Analyze memory usage", "Use dotnet-dump or dotnet-gcdump to analyze memory")
|
||||
.AddShellStep(2, "Force garbage collection", "GC.Collect() - only for diagnostics")
|
||||
.AddManualStep(3, "Review memory allocation patterns", "Look for large object allocations or memory leaks"))
|
||||
.AddManualStep(3, "Review memory allocation patterns", "Look for large object allocations or memory leaks")
|
||||
.WithRunbookUrl("docs/doctor/articles/core/core-env-memory.md"))
|
||||
.WithVerification("stella doctor --check check.core.env.memory")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -7,13 +7,32 @@ namespace StellaOps.Doctor.Plugins.Core.Checks;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that required configuration settings are present and valid.
|
||||
/// Checks multiple key variants to support both appsettings.json and
|
||||
/// environment-variable configuration (Docker compose, Kubernetes, etc.).
|
||||
/// </summary>
|
||||
public sealed class RequiredSettingsCheck : IDoctorCheck
|
||||
{
|
||||
private static readonly string[] RequiredSettings =
|
||||
/// <summary>
|
||||
/// Required settings: at least one key variant must be present.
|
||||
/// The first entry in each group is the canonical name shown in diagnostics.
|
||||
/// </summary>
|
||||
private static readonly string[][] RequiredSettingVariants =
|
||||
[
|
||||
"ConnectionStrings:DefaultConnection",
|
||||
"Logging:LogLevel:Default"
|
||||
// Connection string: multiple key conventions
|
||||
[
|
||||
"ConnectionStrings:DefaultConnection",
|
||||
"ConnectionStrings:Default",
|
||||
"CONNECTIONSTRINGS__DEFAULTCONNECTION",
|
||||
"CONNECTIONSTRINGS__DEFAULT",
|
||||
],
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Recommended (but not required) settings — reported as warnings, not failures.
|
||||
/// </summary>
|
||||
private static readonly string[][] RecommendedSettingVariants =
|
||||
[
|
||||
["Logging:LogLevel:Default"],
|
||||
];
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -41,28 +60,58 @@ public sealed class RequiredSettingsCheck : IDoctorCheck
|
||||
public Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
|
||||
{
|
||||
var result = context.CreateResult(CheckId, "stellaops.doctor.core", DoctorCategory.Core.ToString());
|
||||
|
||||
var config = context.Configuration;
|
||||
|
||||
var missing = new List<string>();
|
||||
var present = new List<string>();
|
||||
var warnings = new List<string>();
|
||||
|
||||
// Check plugin-specific required settings
|
||||
var customRequired = context.PluginConfig.GetSection("RequiredSettings")
|
||||
.Get<string[]>() ?? [];
|
||||
|
||||
var allRequired = RequiredSettings.Concat(customRequired).Distinct();
|
||||
// Check required settings (multiple key variants)
|
||||
foreach (var variants in RequiredSettingVariants)
|
||||
{
|
||||
var canonicalName = variants[0];
|
||||
var found = false;
|
||||
foreach (var variant in variants)
|
||||
{
|
||||
var value = config[variant];
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
// Also check environment variables directly (Docker compose uses __ separator)
|
||||
var envValue = Environment.GetEnvironmentVariable(variant.Replace(":", "__"));
|
||||
if (!string.IsNullOrEmpty(envValue))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var setting in allRequired)
|
||||
if (found) present.Add(canonicalName);
|
||||
else missing.Add(canonicalName);
|
||||
}
|
||||
|
||||
// Check custom required settings (single key)
|
||||
foreach (var setting in customRequired)
|
||||
{
|
||||
var value = config[setting];
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
missing.Add(setting);
|
||||
}
|
||||
else
|
||||
{
|
||||
present.Add(setting);
|
||||
}
|
||||
}
|
||||
|
||||
// Check recommended settings (warning only)
|
||||
foreach (var variants in RecommendedSettingVariants)
|
||||
{
|
||||
var canonicalName = variants[0];
|
||||
var found = variants.Any(v => !string.IsNullOrEmpty(config[v]));
|
||||
if (!found) warnings.Add(canonicalName);
|
||||
}
|
||||
|
||||
if (missing.Count > 0)
|
||||
@@ -74,31 +123,42 @@ public sealed class RequiredSettingsCheck : IDoctorCheck
|
||||
e.Add("MissingCount", missing.Count.ToString());
|
||||
e.Add("PresentCount", present.Count.ToString());
|
||||
e.Add("MissingSettings", string.Join(", ", missing));
|
||||
if (warnings.Count > 0)
|
||||
e.Add("Warnings", string.Join(", ", warnings));
|
||||
})
|
||||
.WithCauses(
|
||||
"Configuration file missing required values",
|
||||
"Environment variables not set",
|
||||
"Secrets not configured")
|
||||
"Database connection string not configured",
|
||||
"Environment variables not set (check Docker compose .env or service environment)")
|
||||
.WithRemediation(r =>
|
||||
{
|
||||
r.AddManualStep(1, "Add missing settings to configuration",
|
||||
$"Add the following settings to appsettings.json or environment: {string.Join(", ", missing)}");
|
||||
|
||||
if (missing.Any(m => m.StartsWith("ConnectionStrings:", StringComparison.Ordinal)))
|
||||
if (missing.Any(m => m.Contains("ConnectionStrings", StringComparison.Ordinal)))
|
||||
{
|
||||
r.AddManualStep(2, "Configure database connection",
|
||||
"Set ConnectionStrings:DefaultConnection in appsettings.json or CONNECTIONSTRINGS__DEFAULTCONNECTION env var");
|
||||
r.AddManualStep(1, "Configure database connection",
|
||||
"Set ConnectionStrings__Default or ConnectionStrings__DefaultConnection as an environment variable, " +
|
||||
"or add ConnectionStrings:Default to appsettings.json. " +
|
||||
"In Docker compose, add to the service environment section in docker-compose.yml.");
|
||||
}
|
||||
else
|
||||
{
|
||||
r.AddManualStep(1, "Add missing settings",
|
||||
$"Configure: {string.Join(", ", missing)}");
|
||||
}
|
||||
r.WithRunbookUrl("docs/doctor/articles/core/core-config-required.md");
|
||||
})
|
||||
.WithVerification("stella doctor --check check.core.config.required")
|
||||
.Build());
|
||||
}
|
||||
|
||||
var msg = $"All {present.Count} required settings are configured";
|
||||
if (warnings.Count > 0)
|
||||
msg += $" ({warnings.Count} recommended setting(s) using defaults: {string.Join(", ", warnings)})";
|
||||
|
||||
return Task.FromResult(result
|
||||
.Pass($"All {present.Count} required settings are configured")
|
||||
.Pass(msg)
|
||||
.WithEvidence("Settings status", e => e
|
||||
.Add("TotalRequired", present.Count.ToString())
|
||||
.Add("AllPresent", "true"))
|
||||
.Add("AllPresent", "true")
|
||||
.Add("RecommendedMissing", warnings.Count.ToString()))
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,8 @@ public sealed class ServiceHealthCheck : IDoctorCheck
|
||||
"External API unreachable")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Check health endpoint", "curl -s http://localhost:5000/health | jq")
|
||||
.AddManualStep(2, "Review failing services", $"Investigate: {string.Join(", ", failedChecks)}"))
|
||||
.AddManualStep(2, "Review failing services", $"Investigate: {string.Join(", ", failedChecks)}")
|
||||
.WithRunbookUrl("docs/doctor/articles/core/core-services-health.md"))
|
||||
.WithVerification("stella doctor --check check.core.services.health")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -99,7 +99,8 @@ public sealed class CryptoLicenseCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Verify license", "Check license file exists and is valid")
|
||||
.AddManualStep(2, "Renew license", "Contact vendor to renew expired licenses")
|
||||
.AddManualStep(3, "Configure license path", "Set Cryptography:<Provider>:LicensePath in configuration"))
|
||||
.AddManualStep(3, "Configure license path", "Set Cryptography:<Provider>:LicensePath in configuration")
|
||||
.WithRunbookUrl("docs/doctor/articles/crypto/crypto-license.md"))
|
||||
.WithVerification("stella doctor --check check.crypto.license")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -154,7 +154,8 @@ public sealed class CryptoProCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Install CryptoPro", "Download and install CryptoPro CSP from cryptopro.ru")
|
||||
.AddManualStep(2, "Set build flag", "Set STELLAOPS_CRYPTO_PRO=1 environment variable")
|
||||
.AddManualStep(3, "Configure license", "Configure Cryptography:CryptoPro:LicensePath"))
|
||||
.AddManualStep(3, "Configure license", "Configure Cryptography:CryptoPro:LicensePath")
|
||||
.WithRunbookUrl("docs/doctor/articles/crypto/crypto-cryptopro.md"))
|
||||
.WithVerification("stella doctor --check check.crypto.cryptopro")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -113,7 +113,8 @@ public sealed class CryptoProviderAvailabilityCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check runtime", "Ensure .NET runtime supports required algorithms")
|
||||
.AddManualStep(2, "Install providers", "Install additional crypto libraries if needed"))
|
||||
.AddManualStep(2, "Install providers", "Install additional crypto libraries if needed")
|
||||
.WithRunbookUrl("docs/doctor/articles/crypto/crypto-provider.md"))
|
||||
.WithVerification("stella doctor --check check.crypto.provider")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -91,7 +91,8 @@ public sealed class EidasProviderCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Configure provider", "Configure PKCS#11 library or certificate store for eIDAS")
|
||||
.AddManualStep(2, "Verify trust list", "Ensure EU Trust List is accessible"))
|
||||
.AddManualStep(2, "Verify trust list", "Ensure EU Trust List is accessible")
|
||||
.WithRunbookUrl("docs/doctor/articles/crypto/crypto-eidas.md"))
|
||||
.WithVerification("stella doctor --check check.crypto.eidas")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -55,7 +55,8 @@ public sealed class FipsComplianceCheck : IDoctorCheck
|
||||
.WithCauses("System FIPS mode is not enabled but configuration requires it")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Enable FIPS on Windows", "Set FIPS security policy in Windows Group Policy")
|
||||
.AddManualStep(2, "Enable FIPS on Linux", "Configure system crypto policy with fips-mode-setup"))
|
||||
.AddManualStep(2, "Enable FIPS on Linux", "Configure system crypto policy with fips-mode-setup")
|
||||
.WithRunbookUrl("docs/doctor/articles/crypto/crypto-fips.md"))
|
||||
.WithVerification("stella doctor --check check.crypto.fips")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -97,7 +97,8 @@ public sealed class GostProviderCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Install provider", "Install the configured GOST provider (OpenSSL GOST engine, CryptoPro CSP, or PKCS#11)")
|
||||
.AddManualStep(2, "Configure endpoint", "For remote providers, configure the service endpoint"))
|
||||
.AddManualStep(2, "Configure endpoint", "For remote providers, configure the service endpoint")
|
||||
.WithRunbookUrl("docs/doctor/articles/crypto/crypto-gost.md"))
|
||||
.WithVerification("stella doctor --check check.crypto.gost")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -115,7 +115,8 @@ public sealed class HsmConnectivityCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Install PKCS#11 library", "Ensure the HSM PKCS#11 library is installed")
|
||||
.AddManualStep(2, "Check connectivity", "Verify network/USB connectivity to HSM")
|
||||
.AddManualStep(3, "Verify credentials", "Ensure HSM credentials are configured"))
|
||||
.AddManualStep(3, "Verify credentials", "Ensure HSM credentials are configured")
|
||||
.WithRunbookUrl("docs/doctor/articles/crypto/crypto-hsm.md"))
|
||||
.WithVerification("stella doctor --check check.crypto.hsm")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -93,7 +93,8 @@ public sealed class SmProviderCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Set environment gate", "Set SM_SOFT_ALLOWED=1 to enable SM software providers")
|
||||
.AddManualStep(2, "Configure SmRemote", "Configure SmRemote:Endpoint for remote SM crypto service"))
|
||||
.AddManualStep(2, "Configure SmRemote", "Configure SmRemote:Endpoint for remote SM crypto service")
|
||||
.WithRunbookUrl("docs/doctor/articles/crypto/crypto-sm.md"))
|
||||
.WithVerification("stella doctor --check check.crypto.sm")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -86,7 +86,8 @@ public sealed class ConnectionPoolHealthCheck : DatabaseCheckBase
|
||||
"Deadlock or lock contention")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Find idle transactions", "psql -c \"SELECT pid, query FROM pg_stat_activity WHERE state = 'idle in transaction'\"")
|
||||
.AddManualStep(2, "Review application code", "Ensure transactions are properly committed or rolled back"))
|
||||
.AddManualStep(2, "Review application code", "Ensure transactions are properly committed or rolled back")
|
||||
.WithRunbookUrl("docs/doctor/articles/postgres/db-pool-health.md"))
|
||||
.WithVerification("stella doctor --check check.db.pool.health")
|
||||
.Build();
|
||||
}
|
||||
@@ -107,7 +108,8 @@ public sealed class ConnectionPoolHealthCheck : DatabaseCheckBase
|
||||
"max_connections too low for workload")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Review connection pool settings", "Check Npgsql connection string pool size")
|
||||
.AddManualStep(2, "Consider increasing max_connections", "Edit postgresql.conf if appropriate"))
|
||||
.AddManualStep(2, "Consider increasing max_connections", "Edit postgresql.conf if appropriate")
|
||||
.WithRunbookUrl("docs/doctor/articles/postgres/db-pool-health.md"))
|
||||
.WithVerification("stella doctor --check check.db.pool.health")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -66,7 +66,8 @@ public sealed class ConnectionPoolSizeCheck : DatabaseCheckBase
|
||||
"Pooling=false in connection string",
|
||||
"Connection string misconfiguration")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Enable pooling", "Set Pooling=true in connection string"))
|
||||
.AddManualStep(1, "Enable pooling", "Set Pooling=true in connection string")
|
||||
.WithRunbookUrl("docs/doctor/articles/postgres/db-pool-size.md"))
|
||||
.WithVerification("stella doctor --check check.db.pool.size")
|
||||
.Build();
|
||||
}
|
||||
@@ -87,7 +88,8 @@ public sealed class ConnectionPoolSizeCheck : DatabaseCheckBase
|
||||
"Multiple application instances sharing connection limit")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Reduce pool size", $"Set Max Pool Size={availableConnections / 2} in connection string")
|
||||
.AddManualStep(2, "Or increase server limit", "Increase max_connections in postgresql.conf"))
|
||||
.AddManualStep(2, "Or increase server limit", "Increase max_connections in postgresql.conf")
|
||||
.WithRunbookUrl("docs/doctor/articles/postgres/db-pool-size.md"))
|
||||
.WithVerification("stella doctor --check check.db.pool.size")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -112,7 +112,8 @@ public sealed class DatabasePermissionsCheck : DatabaseCheckBase
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Create dedicated user", "CREATE USER stellaops WITH PASSWORD 'secure_password'")
|
||||
.AddManualStep(2, "Grant minimal permissions", "GRANT CONNECT ON DATABASE stellaops TO stellaops")
|
||||
.AddManualStep(3, "Update connection string", "Change user in connection string to dedicated user"))
|
||||
.AddManualStep(3, "Update connection string", "Change user in connection string to dedicated user")
|
||||
.WithRunbookUrl("docs/doctor/articles/postgres/db-permissions.md"))
|
||||
.WithVerification("stella doctor --check check.db.permissions")
|
||||
.Build();
|
||||
}
|
||||
@@ -134,7 +135,8 @@ public sealed class DatabasePermissionsCheck : DatabaseCheckBase
|
||||
"Restrictive default privileges")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Grant schema access", $"GRANT USAGE ON SCHEMA public TO {currentUser}")
|
||||
.AddManualStep(2, "Grant table access", $"GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO {currentUser}"))
|
||||
.AddManualStep(2, "Grant table access", $"GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO {currentUser}")
|
||||
.WithRunbookUrl("docs/doctor/articles/postgres/db-permissions.md"))
|
||||
.WithVerification("stella doctor --check check.db.permissions")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -88,7 +88,8 @@ public sealed class FailedMigrationsCheck : DatabaseCheckBase
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Review migration logs", "Check application logs for migration error details")
|
||||
.AddManualStep(2, "Fix migration issues", "Resolve the underlying issue and retry migration")
|
||||
.AddShellStep(3, "Retry migrations", "dotnet ef database update"))
|
||||
.AddShellStep(3, "Retry migrations", "dotnet ef database update")
|
||||
.WithRunbookUrl("docs/doctor/articles/postgres/db-migrations-failed.md"))
|
||||
.WithVerification("stella doctor --check check.db.migrations.failed")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -110,7 +110,8 @@ public sealed class QueryLatencyCheck : DatabaseCheckBase
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Check server load", "psql -c \"SELECT * FROM pg_stat_activity WHERE state = 'active'\"")
|
||||
.AddShellStep(2, "Check for locks", "psql -c \"SELECT * FROM pg_locks WHERE NOT granted\"")
|
||||
.AddManualStep(3, "Review network path", "Check network latency between application and database"))
|
||||
.AddManualStep(3, "Review network path", "Check network latency between application and database")
|
||||
.WithRunbookUrl("docs/doctor/articles/postgres/db-latency.md"))
|
||||
.WithVerification("stella doctor --check check.db.latency")
|
||||
.Build();
|
||||
}
|
||||
@@ -129,7 +130,8 @@ public sealed class QueryLatencyCheck : DatabaseCheckBase
|
||||
"Network latency to database server",
|
||||
"Database server moderately loaded")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Monitor trends", "Track latency over time to identify patterns"))
|
||||
.AddManualStep(1, "Monitor trends", "Track latency over time to identify patterns")
|
||||
.WithRunbookUrl("docs/doctor/articles/postgres/db-latency.md"))
|
||||
.WithVerification("stella doctor --check check.db.latency")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -94,7 +94,8 @@ public sealed class SchemaVersionCheck : DatabaseCheckBase
|
||||
"Manual DDL changes")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "List orphaned FKs", "psql -c \"SELECT conname FROM pg_constraint WHERE NOT convalidated\"")
|
||||
.AddManualStep(2, "Review and clean up", "Drop or fix orphaned constraints"))
|
||||
.AddManualStep(2, "Review and clean up", "Drop or fix orphaned constraints")
|
||||
.WithRunbookUrl("docs/doctor/articles/postgres/db-schema-version.md"))
|
||||
.WithVerification("stella doctor --check check.db.schema.version")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -88,7 +88,8 @@ public sealed class DockerApiVersionCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Update Docker", "Install the latest Docker version for your OS")
|
||||
.AddManualStep(2, "Verify version", "Run: docker version"))
|
||||
.AddManualStep(2, "Verify version", "Run: docker version")
|
||||
.WithRunbookUrl("docs/doctor/articles/docker/docker-apiversion.md"))
|
||||
.WithVerification("stella doctor --check check.docker.apiversion")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -80,7 +80,8 @@ public sealed class DockerDaemonCheck : IDoctorCheck
|
||||
.WithCauses("Docker daemon returned an error response")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check daemon status", "Run: docker info")
|
||||
.AddManualStep(2, "Restart daemon", "Run: sudo systemctl restart docker"))
|
||||
.AddManualStep(2, "Restart daemon", "Run: sudo systemctl restart docker")
|
||||
.WithRunbookUrl("docs/doctor/articles/docker/docker-daemon.md"))
|
||||
.WithVerification("stella doctor --check check.docker.daemon")
|
||||
.Build();
|
||||
}
|
||||
@@ -98,7 +99,8 @@ public sealed class DockerDaemonCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Install Docker", "Follow Docker installation guide for your OS")
|
||||
.AddManualStep(2, "Start daemon", "Run: sudo systemctl start docker")
|
||||
.AddManualStep(3, "Verify installation", "Run: docker version"))
|
||||
.AddManualStep(3, "Verify installation", "Run: docker version")
|
||||
.WithRunbookUrl("docs/doctor/articles/docker/docker-daemon.md"))
|
||||
.WithVerification("stella doctor --check check.docker.daemon")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -97,7 +97,8 @@ public sealed class DockerNetworkCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "List networks", "Run: docker network ls")
|
||||
.AddManualStep(2, "Create network", "Run: docker network create <network-name>"))
|
||||
.AddManualStep(2, "Create network", "Run: docker network create <network-name>")
|
||||
.WithRunbookUrl("docs/doctor/articles/docker/docker-network.md"))
|
||||
.WithVerification("stella doctor --check check.docker.network")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -86,8 +86,28 @@ public sealed class DockerSocketCheck : IDoctorCheck
|
||||
}
|
||||
}
|
||||
|
||||
// Detect if we're running inside a container (no socket is expected)
|
||||
var insideContainer = File.Exists("/.dockerenv") || File.Exists("/proc/1/cgroup");
|
||||
|
||||
if (!socketExists)
|
||||
{
|
||||
if (insideContainer)
|
||||
{
|
||||
// Inside a container without socket mount — this is normal for services
|
||||
// that don't need direct Docker access (like Doctor, Platform, etc.)
|
||||
return result
|
||||
.Pass("Running inside container — Docker socket not required")
|
||||
.WithEvidence("Docker socket", e =>
|
||||
{
|
||||
e.Add("Path", socketPath);
|
||||
e.Add("Exists", "false");
|
||||
e.Add("InsideContainer", "true");
|
||||
e.Add("Note", "Docker socket is not mounted. This is expected for most services. " +
|
||||
"Only mount the socket if this service needs to manage containers.");
|
||||
})
|
||||
.Build();
|
||||
}
|
||||
|
||||
issues.Add($"Docker socket not found at {socketPath}");
|
||||
}
|
||||
else if (!socketReadable || !socketWritable)
|
||||
@@ -98,7 +118,7 @@ public sealed class DockerSocketCheck : IDoctorCheck
|
||||
if (issues.Count > 0)
|
||||
{
|
||||
return result
|
||||
.Fail($"{issues.Count} Docker socket issue(s)")
|
||||
.Warn($"{issues.Count} Docker socket issue(s)")
|
||||
.WithEvidence("Docker socket", e =>
|
||||
{
|
||||
e.Add("Path", socketPath);
|
||||
@@ -109,8 +129,10 @@ public sealed class DockerSocketCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check Docker installation", "Ensure Docker is installed and running")
|
||||
.AddManualStep(2, "Add user to docker group", "Run: sudo usermod -aG docker $USER")
|
||||
.AddManualStep(3, "Re-login", "Log out and back in for group changes to take effect"))
|
||||
.AddManualStep(2, "Mount Docker socket", "Add -v /var/run/docker.sock:/var/run/docker.sock to docker run, " +
|
||||
"or volumes: ['/var/run/docker.sock:/var/run/docker.sock'] in docker-compose.yml")
|
||||
.AddManualStep(3, "Add user to docker group", "Run: sudo usermod -aG docker $USER && logout")
|
||||
.WithRunbookUrl("docs/doctor/articles/docker/docker-socket.md"))
|
||||
.WithVerification("stella doctor --check check.docker.socket")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -123,7 +123,8 @@ public sealed class DockerStorageCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Prune unused data", "Run: docker system prune -a")
|
||||
.AddManualStep(2, "Check disk usage", "Run: docker system df")
|
||||
.AddManualStep(3, "Add storage", "Expand disk or add additional storage"))
|
||||
.AddManualStep(3, "Add storage", "Expand disk or add additional storage")
|
||||
.WithRunbookUrl("docs/doctor/articles/docker/docker-storage.md"))
|
||||
.WithVerification("stella doctor --check check.docker.storage")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ public sealed class CiSystemConnectivityCheck : IDoctorCheck
|
||||
rb.AddStep(2, "Refresh credentials",
|
||||
$"stella ci auth refresh {unhealthy[0]}",
|
||||
CommandType.Manual);
|
||||
rb.WithRunbookUrl("docs/doctor/articles/integration/integration-ci-system.md");
|
||||
})
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build();
|
||||
@@ -145,6 +146,7 @@ public sealed class CiSystemConnectivityCheck : IDoctorCheck
|
||||
rb.AddStep(1, "Check runner status",
|
||||
$"stella ci runners {noRunners[0]}",
|
||||
CommandType.Shell);
|
||||
rb.WithRunbookUrl("docs/doctor/articles/integration/integration-ci-system.md");
|
||||
})
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build();
|
||||
|
||||
@@ -126,7 +126,8 @@ public sealed class GitProviderCheck : IDoctorCheck
|
||||
"Git provider service is down")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Verify Git URL", "Check Git:Url configuration")
|
||||
.AddManualStep(2, "Test connectivity", $"curl -v {gitUrl}"))
|
||||
.AddManualStep(2, "Test connectivity", $"curl -v {gitUrl}")
|
||||
.WithRunbookUrl("docs/doctor/articles/integration/integration-git.md"))
|
||||
.WithVerification("stella doctor --check check.integration.git")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -174,7 +174,8 @@ public sealed class IntegrationWebhookHealthCheck : IDoctorCheck
|
||||
.WithRemediation(rb => rb
|
||||
.AddStep(1, "Monitor webhook metrics",
|
||||
"stella webhooks stats",
|
||||
CommandType.Shell))
|
||||
CommandType.Shell)
|
||||
.WithRunbookUrl("docs/doctor/articles/integration/integration-webhooks.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -95,7 +95,8 @@ public sealed class LdapConnectivityCheck : IDoctorCheck
|
||||
"Network connectivity issues")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check LDAP server", "Verify LDAP server is running and accessible")
|
||||
.AddManualStep(2, "Test connectivity", $"telnet {host} {port}"))
|
||||
.AddManualStep(2, "Test connectivity", $"telnet {host} {port}")
|
||||
.WithRunbookUrl("docs/doctor/articles/integration/integration-ldap.md"))
|
||||
.WithVerification("stella doctor --check check.integration.ldap")
|
||||
.Build();
|
||||
}
|
||||
@@ -143,7 +144,8 @@ public sealed class LdapConnectivityCheck : IDoctorCheck
|
||||
"Network unreachable")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check LDAP configuration", "Verify Ldap:Host and Ldap:Port settings")
|
||||
.AddManualStep(2, "Check DNS", $"nslookup {host}"))
|
||||
.AddManualStep(2, "Check DNS", $"nslookup {host}")
|
||||
.WithRunbookUrl("docs/doctor/articles/integration/integration-ldap.md"))
|
||||
.WithVerification("stella doctor --check check.integration.ldap")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -89,7 +89,8 @@ public sealed class ObjectStorageCheck : IDoctorCheck
|
||||
"Firewall blocking connection")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check S3 endpoint", "Verify S3:Endpoint configuration")
|
||||
.AddManualStep(2, "Test connectivity", $"curl -v {endpoint}"))
|
||||
.AddManualStep(2, "Test connectivity", $"curl -v {endpoint}")
|
||||
.WithRunbookUrl("docs/doctor/articles/integration/integration-s3-storage.md"))
|
||||
.WithVerification("stella doctor --check check.integration.s3.storage")
|
||||
.Build();
|
||||
}
|
||||
@@ -148,7 +149,8 @@ public sealed class ObjectStorageCheck : IDoctorCheck
|
||||
"Network unreachable")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check S3 service", "Verify MinIO or S3 service is running")
|
||||
.AddManualStep(2, "Check DNS", $"nslookup {new Uri(endpoint).Host}"))
|
||||
.AddManualStep(2, "Check DNS", $"nslookup {new Uri(endpoint).Host}")
|
||||
.WithRunbookUrl("docs/doctor/articles/integration/integration-s3-storage.md"))
|
||||
.WithVerification("stella doctor --check check.integration.s3.storage")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -114,7 +114,8 @@ public sealed class OciRegistryCheck : IDoctorCheck
|
||||
"Registry service is down")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Verify registry URL", "Check OCI:RegistryUrl configuration")
|
||||
.AddManualStep(2, "Test connectivity", $"curl -v {registryUrl}/v2/"))
|
||||
.AddManualStep(2, "Test connectivity", $"curl -v {registryUrl}/v2/")
|
||||
.WithRunbookUrl("docs/doctor/articles/integration/integration-oci-registry.md"))
|
||||
.WithVerification("stella doctor --check check.integration.oci.registry")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -129,7 +129,8 @@ public sealed class OidcProviderCheck : IDoctorCheck
|
||||
"OIDC provider does not support discovery")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Verify issuer URL", "Check Oidc:Issuer configuration")
|
||||
.AddManualStep(2, "Test discovery", $"curl -v {discoveryUrl}"))
|
||||
.AddManualStep(2, "Test discovery", $"curl -v {discoveryUrl}")
|
||||
.WithRunbookUrl("docs/doctor/articles/integration/integration-oidc.md"))
|
||||
.WithVerification("stella doctor --check check.integration.oidc")
|
||||
.Build();
|
||||
}
|
||||
@@ -149,7 +150,8 @@ public sealed class OidcProviderCheck : IDoctorCheck
|
||||
"OIDC provider is down")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Verify issuer URL", "Check Oidc:Issuer configuration")
|
||||
.AddManualStep(2, "Test connectivity", $"curl -v {issuer}/.well-known/openid-configuration"))
|
||||
.AddManualStep(2, "Test connectivity", $"curl -v {issuer}/.well-known/openid-configuration")
|
||||
.WithRunbookUrl("docs/doctor/articles/integration/integration-oidc.md"))
|
||||
.WithVerification("stella doctor --check check.integration.oidc")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ public sealed class SecretsManagerConnectivityCheck : IDoctorCheck
|
||||
rb.AddStep(2, "Refresh authentication",
|
||||
$"stella secrets auth refresh {unhealthy[0]}",
|
||||
CommandType.Manual);
|
||||
rb.WithRunbookUrl("docs/doctor/articles/integration/integration-secrets-manager.md");
|
||||
})
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build();
|
||||
@@ -149,6 +150,7 @@ public sealed class SecretsManagerConnectivityCheck : IDoctorCheck
|
||||
rb.AddStep(2, "Check seal status",
|
||||
$"stella secrets status {sealed_[0]}",
|
||||
CommandType.Shell);
|
||||
rb.WithRunbookUrl("docs/doctor/articles/integration/integration-secrets-manager.md");
|
||||
})
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build();
|
||||
|
||||
@@ -90,7 +90,8 @@ public sealed class SmtpCheck : IDoctorCheck
|
||||
"Network connectivity issues")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check SMTP server", "Verify SMTP server is running")
|
||||
.AddManualStep(2, "Test connectivity", $"telnet {host} {port}"))
|
||||
.AddManualStep(2, "Test connectivity", $"telnet {host} {port}")
|
||||
.WithRunbookUrl("docs/doctor/articles/integration/integration-smtp.md"))
|
||||
.WithVerification("stella doctor --check check.integration.smtp")
|
||||
.Build();
|
||||
}
|
||||
@@ -138,7 +139,8 @@ public sealed class SmtpCheck : IDoctorCheck
|
||||
"Network unreachable")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check SMTP configuration", "Verify Smtp:Host and Smtp:Port settings")
|
||||
.AddManualStep(2, "Check DNS", $"nslookup {host}"))
|
||||
.AddManualStep(2, "Check DNS", $"nslookup {host}")
|
||||
.WithRunbookUrl("docs/doctor/articles/integration/integration-smtp.md"))
|
||||
.WithVerification("stella doctor --check check.integration.smtp")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -128,7 +128,8 @@ public sealed class NotifyChannelConfigurationCheck : IDoctorCheck
|
||||
CommandType.FileEdit)
|
||||
.AddStep(2, "Or run setup wizard",
|
||||
"stella setup --step notify",
|
||||
CommandType.Shell))
|
||||
CommandType.Shell)
|
||||
.WithRunbookUrl("docs/doctor/articles/notify/notify-channel-configured.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
@@ -145,7 +146,8 @@ public sealed class NotifyChannelConfigurationCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Review configuration", "Check Notify:Channels section for missing values")
|
||||
.AddStep(2, "Run setup wizard", "stella setup --step notify", CommandType.Shell))
|
||||
.AddStep(2, "Run setup wizard", "stella setup --step notify", CommandType.Shell)
|
||||
.WithRunbookUrl("docs/doctor/articles/notify/notify-channel-configured.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
@@ -161,7 +163,8 @@ public sealed class NotifyChannelConfigurationCheck : IDoctorCheck
|
||||
})
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Review configuration", "Check Notify:Channels section for missing values"))
|
||||
.AddManualStep(1, "Review configuration", "Check Notify:Channels section for missing values")
|
||||
.WithRunbookUrl("docs/doctor/articles/notify/notify-channel-configured.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ public sealed class NotifyChannelConnectivityCheck : IDoctorCheck
|
||||
{
|
||||
r.AddManualStep(4, "Check webhook endpoint", "Verify webhook endpoint is accessible from this network");
|
||||
}
|
||||
r.WithRunbookUrl("docs/doctor/articles/notify/notify-channel-connectivity.md");
|
||||
})
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build();
|
||||
|
||||
@@ -111,7 +111,8 @@ public sealed class NotifyDeliveryTestCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Review delivery settings", "Check Notify:Delivery section for invalid values")
|
||||
.AddStep(2, "Run setup wizard", "stella setup --step notify", CommandType.Shell))
|
||||
.AddStep(2, "Run setup wizard", "stella setup --step notify", CommandType.Shell)
|
||||
.WithRunbookUrl("docs/doctor/articles/notify/notify-delivery-test.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
@@ -139,7 +140,8 @@ public sealed class NotifyDeliveryTestCheck : IDoctorCheck
|
||||
" \"Redis\": { \"ConnectionString\": \"localhost:6379\" }\n" +
|
||||
" }\n" +
|
||||
"}",
|
||||
CommandType.FileEdit))
|
||||
CommandType.FileEdit)
|
||||
.WithRunbookUrl("docs/doctor/articles/notify/notify-delivery-test.md"))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -115,7 +115,8 @@ public sealed class HealthCheckEndpointsCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Configure endpoints", "Set separate /health/ready and /health/live endpoints")
|
||||
.AddManualStep(2, "Set timeout", "Configure reasonable timeout (5-30 seconds)"))
|
||||
.AddManualStep(2, "Set timeout", "Configure reasonable timeout (5-30 seconds)")
|
||||
.WithRunbookUrl("docs/doctor/articles/observability/observability-healthchecks.md"))
|
||||
.WithVerification("stella doctor --check check.observability.healthchecks")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -89,7 +89,8 @@ public sealed class LoggingConfigurationCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Set appropriate level", "Use 'Information' or 'Warning' for production")
|
||||
.AddManualStep(2, "Enable structured logging", "Configure Serilog or JSON console formatter"))
|
||||
.AddManualStep(2, "Enable structured logging", "Configure Serilog or JSON console formatter")
|
||||
.WithRunbookUrl("docs/doctor/articles/observability/observability-logging.md"))
|
||||
.WithVerification("stella doctor --check check.observability.logging")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -118,7 +118,8 @@ public sealed class MetricsCollectionCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Enable metrics", "Configure Metrics:Enabled or Prometheus:Enabled")
|
||||
.AddManualStep(2, "Check endpoint", $"curl http://localhost:{metricsPort ?? 80}{metricsPath}"))
|
||||
.AddManualStep(2, "Check endpoint", $"curl http://localhost:{metricsPort ?? 80}{metricsPath}")
|
||||
.WithRunbookUrl("docs/doctor/articles/observability/observability-metrics.md"))
|
||||
.WithVerification("stella doctor --check check.observability.metrics")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -125,7 +125,8 @@ public sealed class OpenTelemetryCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Set service name", "Configure OTEL_SERVICE_NAME environment variable")
|
||||
.AddManualStep(2, "Verify endpoint", "Ensure OpenTelemetry collector is running"))
|
||||
.AddManualStep(2, "Verify endpoint", "Ensure OpenTelemetry collector is running")
|
||||
.WithRunbookUrl("docs/doctor/articles/observability/observability-otel.md"))
|
||||
.WithVerification("stella doctor --check check.observability.otel")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -116,7 +116,8 @@ public sealed class TracingConfigurationCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Set sampling ratio", "Configure Tracing:SamplingRatio between 0.01 and 1.0")
|
||||
.AddManualStep(2, "Enable instrumentation", "Enable HTTP and database instrumentation"))
|
||||
.AddManualStep(2, "Enable instrumentation", "Enable HTTP and database instrumentation")
|
||||
.WithRunbookUrl("docs/doctor/articles/observability/observability-tracing.md"))
|
||||
.WithVerification("stella doctor --check check.observability.tracing")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -127,7 +127,8 @@ public sealed class ApiKeySecurityCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Set minimum length", "Configure ApiKey:MinLength to at least 32")
|
||||
.AddManualStep(2, "Disable query string", "Set ApiKey:AllowInQueryString to false")
|
||||
.AddManualStep(3, "Enable rate limiting", "Set ApiKey:RateLimitPerKey to true"))
|
||||
.AddManualStep(3, "Enable rate limiting", "Set ApiKey:RateLimitPerKey to true")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-apikey.md"))
|
||||
.WithVerification("stella doctor --check check.security.apikey")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -67,7 +67,8 @@ public sealed class AuditLoggingCheck : IDoctorCheck
|
||||
})
|
||||
.WithCauses("Audit logging disabled in configuration")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Enable audit logging", "Set Audit:Enabled to true"))
|
||||
.AddManualStep(1, "Enable audit logging", "Set Audit:Enabled to true")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-audit-logging.md"))
|
||||
.WithVerification("stella doctor --check check.security.audit.logging")
|
||||
.Build());
|
||||
}
|
||||
@@ -108,7 +109,8 @@ public sealed class AuditLoggingCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Enable audit logging", "Set Audit:Enabled to true")
|
||||
.AddManualStep(2, "Configure events", "Enable logging for auth, access, and admin events")
|
||||
.AddManualStep(3, "Set destination", "Configure audit log destination"))
|
||||
.AddManualStep(3, "Set destination", "Configure audit log destination")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-audit-logging.md"))
|
||||
.WithVerification("stella doctor --check check.security.audit.logging")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -101,7 +101,8 @@ public sealed class CorsConfigurationCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Specify origins", "Configure explicit allowed origins in Cors:AllowedOrigins")
|
||||
.AddManualStep(2, "Use HTTPS", "Ensure all allowed origins use HTTPS"))
|
||||
.AddManualStep(2, "Use HTTPS", "Ensure all allowed origins use HTTPS")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-cors.md"))
|
||||
.WithVerification("stella doctor --check check.security.cors")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -97,7 +97,8 @@ public sealed class EncryptionKeyCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Use strong algorithm", "Configure AES-256 or stronger")
|
||||
.AddManualStep(2, "Set key rotation", "Configure Encryption:KeyRotationDays"))
|
||||
.AddManualStep(2, "Set key rotation", "Configure Encryption:KeyRotationDays")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-encryption.md"))
|
||||
.WithVerification("stella doctor --check check.security.encryption")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -77,7 +77,8 @@ public sealed class EvidenceIntegrityCheck : IDoctorCheck
|
||||
.WithCauses("Evidence locker has not been initialized", "Path is incorrect")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Create directory", $"mkdir -p {evidenceLockerPath}")
|
||||
.AddManualStep(2, "Check configuration", "Verify EvidenceLocker:LocalPath setting"))
|
||||
.AddManualStep(2, "Check configuration", "Verify EvidenceLocker:LocalPath setting")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-evidence-integrity.md"))
|
||||
.WithVerification("stella doctor --check check.security.evidence.integrity")
|
||||
.Build();
|
||||
}
|
||||
@@ -162,7 +163,8 @@ public sealed class EvidenceIntegrityCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Review issues", "Examine the invalid files listed above")
|
||||
.AddManualStep(2, "Re-generate evidence", "Re-scan and re-sign affected evidence bundles")
|
||||
.AddManualStep(3, "Check Rekor", "Verify transparency log entries are valid"))
|
||||
.AddManualStep(3, "Check Rekor", "Verify transparency log entries are valid")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-evidence-integrity.md"))
|
||||
.WithVerification("stella doctor --check check.security.evidence.integrity")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -115,7 +115,8 @@ public sealed class JwtConfigurationCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Configure JWT settings", "Set Jwt:SigningKey, Jwt:Issuer, and Jwt:Audience")
|
||||
.AddManualStep(2, "Use strong key", "Ensure signing key is at least 32 characters")
|
||||
.AddManualStep(3, "Consider RS256", "Use asymmetric algorithms for production"))
|
||||
.AddManualStep(3, "Consider RS256", "Use asymmetric algorithms for production")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-jwt-config.md"))
|
||||
.WithVerification("stella doctor --check check.security.jwt.config")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -132,7 +132,8 @@ public sealed class PasswordPolicyCheck : IDoctorCheck
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Increase minimum length", "Set Identity:Password:RequiredLength to at least 12")
|
||||
.AddManualStep(2, "Enable complexity", "Require digits, uppercase, lowercase, and special characters"))
|
||||
.AddManualStep(2, "Enable complexity", "Require digits, uppercase, lowercase, and special characters")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-password-policy.md"))
|
||||
.WithVerification("stella doctor --check check.security.password.policy")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -63,7 +63,8 @@ public sealed class RateLimitingCheck : IDoctorCheck
|
||||
})
|
||||
.WithCauses("Rate limiting explicitly disabled in configuration")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Enable rate limiting", "Set RateLimiting:Enabled to true"))
|
||||
.AddManualStep(1, "Enable rate limiting", "Set RateLimiting:Enabled to true")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-ratelimit.md"))
|
||||
.WithVerification("stella doctor --check check.security.ratelimit")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -37,10 +37,11 @@ public sealed class SecretsConfigurationCheck : IDoctorCheck
|
||||
|
||||
var issues = new List<string>();
|
||||
|
||||
// Only check actual secret values (keys, passwords, tokens).
|
||||
// Connection strings are NOT secrets — they are DSNs that contain host/port/db
|
||||
// and are expected in configuration for all deployment modes.
|
||||
var sensitiveKeys = new[]
|
||||
{
|
||||
"ConnectionStrings:Default",
|
||||
"Database:ConnectionString",
|
||||
"Jwt:SigningKey",
|
||||
"Jwt:Secret",
|
||||
"ApiKey",
|
||||
@@ -77,7 +78,8 @@ public sealed class SecretsConfigurationCheck : IDoctorCheck
|
||||
|
||||
if (!useSecretManager && issues.Count > 0)
|
||||
{
|
||||
issues.Add("No secrets management provider configured");
|
||||
// Only flag missing secrets manager if actual plaintext secrets were found
|
||||
issues.Add("Consider configuring a secrets management provider (Vault, Azure Key Vault, or dotnet user-secrets)");
|
||||
}
|
||||
|
||||
if (issues.Count > 0)
|
||||
@@ -94,7 +96,8 @@ public sealed class SecretsConfigurationCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Use secrets manager", "Configure a secrets provider like HashiCorp Vault or Azure Key Vault")
|
||||
.AddManualStep(2, "Use environment variables", "Move secrets to environment variables")
|
||||
.AddManualStep(3, "Use user secrets", "Use dotnet user-secrets for development"))
|
||||
.AddManualStep(3, "Use user secrets", "Use dotnet user-secrets for development")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-secrets.md"))
|
||||
.WithVerification("stella doctor --check check.security.secrets")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -97,7 +97,8 @@ public sealed class SecurityHeadersCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Enable HSTS", "Set Security:Headers:Hsts:Enabled to true")
|
||||
.AddManualStep(2, "Set X-Frame-Options", "Configure as DENY or SAMEORIGIN")
|
||||
.AddManualStep(3, "Configure CSP", "Set a Content-Security-Policy appropriate for your app"))
|
||||
.AddManualStep(3, "Configure CSP", "Set a Content-Security-Policy appropriate for your app")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-headers.md"))
|
||||
.WithVerification("stella doctor --check check.security.headers")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -66,7 +66,8 @@ public sealed class TlsCertificateCheck : IDoctorCheck
|
||||
.WithCauses("Certificate file path is incorrect", "Certificate file was deleted")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Verify path", "Check Tls:CertificatePath configuration")
|
||||
.AddManualStep(2, "Generate certificate", "Generate or obtain a valid TLS certificate"))
|
||||
.AddManualStep(2, "Generate certificate", "Generate or obtain a valid TLS certificate")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-tls-certificate.md"))
|
||||
.WithVerification("stella doctor --check check.security.tls.certificate")
|
||||
.Build());
|
||||
}
|
||||
@@ -112,7 +113,8 @@ public sealed class TlsCertificateCheck : IDoctorCheck
|
||||
.WithCauses("Certificate has exceeded its validity period")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Renew certificate", "Obtain a new TLS certificate")
|
||||
.AddManualStep(2, "Update configuration", "Update Tls:CertificatePath with new certificate"))
|
||||
.AddManualStep(2, "Update configuration", "Update Tls:CertificatePath with new certificate")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-tls-certificate.md"))
|
||||
.WithVerification("stella doctor --check check.security.tls.certificate")
|
||||
.Build());
|
||||
}
|
||||
@@ -130,7 +132,8 @@ public sealed class TlsCertificateCheck : IDoctorCheck
|
||||
})
|
||||
.WithCauses("Certificate is approaching expiration")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Plan renewal", "Schedule certificate renewal before expiration"))
|
||||
.AddManualStep(1, "Plan renewal", "Schedule certificate renewal before expiration")
|
||||
.WithRunbookUrl("docs/doctor/articles/security/security-tls-certificate.md"))
|
||||
.Build());
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,8 @@ public sealed class BackendConnectivityCheck : IDoctorCheck
|
||||
"Authentication/authorization failure")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check backend logs", "kubectl logs -l app=stellaops-backend")
|
||||
.AddManualStep(2, "Verify backend health", $"curl -v {healthUrl}"))
|
||||
.AddManualStep(2, "Verify backend health", $"curl -v {healthUrl}")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.servicegraph.backend")
|
||||
.Build();
|
||||
}
|
||||
@@ -149,7 +150,8 @@ public sealed class BackendConnectivityCheck : IDoctorCheck
|
||||
"Firewall blocking connection")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Verify URL", "Check STELLAOPS_BACKEND_URL environment variable")
|
||||
.AddManualStep(2, "Test connectivity", $"curl -v {backendUrl}/health"))
|
||||
.AddManualStep(2, "Test connectivity", $"curl -v {backendUrl}/health")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.servicegraph.backend")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ public sealed class CircuitBreakerStatusCheck : IDoctorCheck
|
||||
.WithEvidence(evidenceBuilder.Build("Circuit breaker configuration"))
|
||||
.WithCauses("Break duration less than 5 seconds may cause excessive retries")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Increase break duration", "Set Resilience:CircuitBreaker:BreakDurationSeconds to 30"))
|
||||
.AddManualStep(1, "Increase break duration", "Set Resilience:CircuitBreaker:BreakDurationSeconds to 30")
|
||||
.WithRunbookUrl(""))
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -85,7 +86,8 @@ public sealed class CircuitBreakerStatusCheck : IDoctorCheck
|
||||
.WithEvidence(evidenceBuilder.Build("Circuit breaker configuration"))
|
||||
.WithCauses("Threshold of 1 may cause circuit to open on transient failures")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Increase threshold", "Set Resilience:CircuitBreaker:FailureThreshold to 5"))
|
||||
.AddManualStep(1, "Increase threshold", "Set Resilience:CircuitBreaker:FailureThreshold to 5")
|
||||
.WithRunbookUrl(""))
|
||||
.Build());
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,8 @@ public sealed class MessageQueueCheck : IDoctorCheck
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check RabbitMQ status", "docker ps | grep rabbitmq")
|
||||
.AddManualStep(2, "Check RabbitMQ logs", "docker logs rabbitmq")
|
||||
.AddManualStep(3, "Start RabbitMQ", "docker-compose up -d rabbitmq"))
|
||||
.AddManualStep(3, "Start RabbitMQ", "docker-compose up -d rabbitmq")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.servicegraph.mq")
|
||||
.Build();
|
||||
}
|
||||
@@ -132,7 +133,8 @@ public sealed class MessageQueueCheck : IDoctorCheck
|
||||
"Network unreachable")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Start RabbitMQ", "docker-compose up -d rabbitmq")
|
||||
.AddManualStep(2, "Verify DNS", $"nslookup {rabbitHost}"))
|
||||
.AddManualStep(2, "Verify DNS", $"nslookup {rabbitHost}")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.servicegraph.mq")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -114,7 +114,8 @@ public sealed class ServiceEndpointsCheck : IDoctorCheck
|
||||
.WithCauses(failedServices.Select(s => $"{s} service is down or unreachable").ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check service status", "kubectl get pods -l app=stellaops")
|
||||
.AddManualStep(2, "Check service logs", "kubectl logs -l app=stellaops --tail=100"))
|
||||
.AddManualStep(2, "Check service logs", "kubectl logs -l app=stellaops --tail=100")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.servicegraph.endpoints")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -90,7 +90,8 @@ public sealed class ServiceTimeoutCheck : IDoctorCheck
|
||||
.WithEvidence(evidenceBuilder.Build("Timeout configuration"))
|
||||
.WithCauses(issues.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Review timeout values", "Check configuration and adjust timeouts based on expected service latencies"))
|
||||
.AddManualStep(1, "Review timeout values", "Check configuration and adjust timeouts based on expected service latencies")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.servicegraph.timeouts")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ public sealed class ValkeyConnectivityCheck : IDoctorCheck
|
||||
.WithEvidence("Configuration", e => e.Add("ConnectionString", RedactConnectionString(connectionString)))
|
||||
.WithCauses("Connection string format is invalid")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Fix connection string", "Use format: host:port or host:port,password=xxx"))
|
||||
.AddManualStep(1, "Fix connection string", "Use format: host:port or host:port,password=xxx")
|
||||
.WithRunbookUrl(""))
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -96,7 +97,8 @@ public sealed class ValkeyConnectivityCheck : IDoctorCheck
|
||||
"Firewall blocking port " + port)
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check Valkey status", "docker ps | grep valkey")
|
||||
.AddManualStep(2, "Test port connectivity", $"nc -zv {host} {port}"))
|
||||
.AddManualStep(2, "Test port connectivity", $"nc -zv {host} {port}")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.servicegraph.valkey")
|
||||
.Build();
|
||||
}
|
||||
@@ -148,7 +150,8 @@ public sealed class ValkeyConnectivityCheck : IDoctorCheck
|
||||
"Network unreachable")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Start Valkey", "docker-compose up -d valkey")
|
||||
.AddManualStep(2, "Check DNS", $"nslookup {host}"))
|
||||
.AddManualStep(2, "Check DNS", $"nslookup {host}")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.servicegraph.valkey")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -94,7 +94,8 @@ public sealed class MirrorServerAuthCheck : IDoctorCheck
|
||||
"Missing required OAuth issuer configuration")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Configure OAuth settings", "Add 'sources:mirrorServer:oauth' section with issuer URL")
|
||||
.AddShellStep(2, "Run setup wizard", "stella setup --step sources"))
|
||||
.AddShellStep(2, "Run setup wizard", "stella setup --step sources")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.sources.mirror.auth")
|
||||
.Build());
|
||||
}
|
||||
@@ -112,7 +113,8 @@ public sealed class MirrorServerAuthCheck : IDoctorCheck
|
||||
.WithCauses("OAuth Issuer URL not configured")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Configure OAuth issuer", "Set 'sources:mirrorServer:oauth:issuer' to your OIDC provider URL")
|
||||
.AddShellStep(2, "Verify issuer metadata", "curl -s {issuer}/.well-known/openid-configuration"))
|
||||
.AddShellStep(2, "Verify issuer metadata", "curl -s {issuer}/.well-known/openid-configuration")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.sources.mirror.auth")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -133,7 +133,8 @@ public sealed class MirrorServerRateLimitCheck : IDoctorCheck
|
||||
.WithCauses(warnings.ToArray())
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Review rate limit configuration", "Check sources:mirrorServer:rateLimits in configuration")
|
||||
.AddManualStep(2, "Set appropriate limits", "Configure MaxRequests and PerSeconds for your expected traffic"))
|
||||
.AddManualStep(2, "Set appropriate limits", "Configure MaxRequests and PerSeconds for your expected traffic")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.sources.mirror.ratelimit")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -121,7 +121,8 @@ public sealed class SourceConnectivityCheck : IDoctorCheck
|
||||
}
|
||||
})
|
||||
.WithCauses(checkResult.PossibleReasons.ToArray())
|
||||
.WithRemediation(r => BuildRemediation(r, checkResult))
|
||||
.WithRemediation(r => BuildRemediation(r, checkResult
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build();
|
||||
}
|
||||
@@ -148,7 +149,8 @@ public sealed class SourceConnectivityCheck : IDoctorCheck
|
||||
}
|
||||
})
|
||||
.WithCauses(checkResult.PossibleReasons.ToArray())
|
||||
.WithRemediation(r => BuildRemediation(r, checkResult))
|
||||
.WithRemediation(r => BuildRemediation(r, checkResult
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check {CheckId}")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -61,7 +61,8 @@ public sealed class SourceModeConfiguredCheck : IDoctorCheck
|
||||
"Configuration not loaded properly")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Add sources section to configuration", "Add 'sources:' section to appsettings.json or environment-specific config")
|
||||
.AddShellStep(2, "Run setup wizard", "stella setup --step sources"))
|
||||
.AddShellStep(2, "Run setup wizard", "stella setup --step sources")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.sources.mode.configured")
|
||||
.Build());
|
||||
}
|
||||
@@ -87,7 +88,8 @@ public sealed class SourceModeConfiguredCheck : IDoctorCheck
|
||||
"Mirror server URL not specified")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Configure mirror server", "Add 'sources:mirrorServer' section with URL and authentication settings")
|
||||
.AddShellStep(2, "Run setup wizard", "stella setup --step sources"))
|
||||
.AddShellStep(2, "Run setup wizard", "stella setup --step sources")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification("stella doctor --check check.sources.mode.configured")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ public sealed class PolicyEngineCheck : VerificationCheckBase
|
||||
.Add("BundlePath", bundlePath)
|
||||
.Add("FileExists", "false"))
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Export bundle", "stella verification bundle export --include-policy --output " + bundlePath))
|
||||
.AddShellStep(1, "Export bundle", "stella verification bundle export --include-policy --output " + bundlePath)
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.policy.engine")
|
||||
.Build());
|
||||
}
|
||||
@@ -101,7 +102,8 @@ public sealed class PolicyEngineCheck : VerificationCheckBase
|
||||
"Bundle was exported without policy results",
|
||||
"Policy evaluation not run before export")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Re-export with policy", "stella verification bundle export --include-policy --output " + bundlePath))
|
||||
.AddShellStep(1, "Re-export with policy", "stella verification bundle export --include-policy --output " + bundlePath)
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.policy.engine")
|
||||
.Build());
|
||||
}
|
||||
@@ -158,7 +160,8 @@ public sealed class PolicyEngineCheck : VerificationCheckBase
|
||||
.WithCauses("Policy engine not configured or disabled")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Enable policy engine", "Set Policy:Engine:Enabled to true")
|
||||
.AddManualStep(2, "Configure default policy", "Set Policy:DefaultPolicyRef to a policy reference"))
|
||||
.AddManualStep(2, "Configure default policy", "Set Policy:DefaultPolicyRef to a policy reference")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.policy.engine")
|
||||
.Build());
|
||||
}
|
||||
@@ -176,7 +179,8 @@ public sealed class PolicyEngineCheck : VerificationCheckBase
|
||||
.WithCauses("No test policy reference configured")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Configure test policy", "Set Doctor:Plugins:Verification:PolicyTest:PolicyRef")
|
||||
.AddManualStep(2, "Or set default", "Set Policy:DefaultPolicyRef for a default policy"))
|
||||
.AddManualStep(2, "Or set default", "Set Policy:DefaultPolicyRef for a default policy")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.policy.engine")
|
||||
.Build());
|
||||
}
|
||||
@@ -198,7 +202,8 @@ public sealed class PolicyEngineCheck : VerificationCheckBase
|
||||
.WithCauses("Policy may not consider VEX statements when evaluating vulnerabilities")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Enable VEX in policy", "Set Policy:VexAware to true")
|
||||
.AddManualStep(2, "Update policy rules", "Ensure policy considers VEX justifications for vulnerabilities"))
|
||||
.AddManualStep(2, "Update policy rules", "Ensure policy considers VEX justifications for vulnerabilities")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.policy.engine")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -76,7 +76,8 @@ public sealed class SbomValidationCheck : VerificationCheckBase
|
||||
.Add("BundlePath", bundlePath)
|
||||
.Add("FileExists", "false"))
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Export bundle", "stella verification bundle export --include-sbom --output " + bundlePath))
|
||||
.AddShellStep(1, "Export bundle", "stella verification bundle export --include-sbom --output " + bundlePath)
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.sbom.validation")
|
||||
.Build());
|
||||
}
|
||||
@@ -101,7 +102,8 @@ public sealed class SbomValidationCheck : VerificationCheckBase
|
||||
"Test artifact has no SBOM attached")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Re-export with SBOM", "stella verification bundle export --include-sbom --output " + bundlePath)
|
||||
.AddManualStep(2, "Generate SBOM", "Enable SBOM generation in your build pipeline"))
|
||||
.AddManualStep(2, "Generate SBOM", "Enable SBOM generation in your build pipeline")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.sbom.validation")
|
||||
.Build());
|
||||
}
|
||||
@@ -157,7 +159,8 @@ public sealed class SbomValidationCheck : VerificationCheckBase
|
||||
"SBOM attestation not configured")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Enable SBOM generation", "Set Scanner:SbomGeneration:Enabled to true")
|
||||
.AddManualStep(2, "Enable SBOM attestation", "Set Attestor:SbomAttestation:Enabled to true"))
|
||||
.AddManualStep(2, "Enable SBOM attestation", "Set Attestor:SbomAttestation:Enabled to true")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.sbom.validation")
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@ public sealed class SignatureVerificationCheck : VerificationCheckBase
|
||||
.Add("BundlePath", bundlePath)
|
||||
.Add("FileExists", "false"))
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Export bundle", "stella verification bundle export --output " + bundlePath))
|
||||
.AddShellStep(1, "Export bundle", "stella verification bundle export --output " + bundlePath)
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.signature")
|
||||
.Build());
|
||||
}
|
||||
@@ -102,7 +103,8 @@ public sealed class SignatureVerificationCheck : VerificationCheckBase
|
||||
.Add("SignatureDataFound", "false")
|
||||
.Add("Note", "Bundle should contain DSSE signatures for verification"))
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Re-export with signatures", "stella verification bundle export --include-signatures --output " + bundlePath))
|
||||
.AddShellStep(1, "Re-export with signatures", "stella verification bundle export --include-signatures --output " + bundlePath)
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.signature")
|
||||
.Build());
|
||||
}
|
||||
@@ -154,7 +156,8 @@ public sealed class SignatureVerificationCheck : VerificationCheckBase
|
||||
.Add("Note", "Enable Sigstore to verify artifact signatures"))
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Enable Sigstore", "Set Sigstore:Enabled to true")
|
||||
.AddManualStep(2, "Configure signing", "Set up signing keys or keyless mode"))
|
||||
.AddManualStep(2, "Configure signing", "Set up signing keys or keyless mode")
|
||||
.WithRunbookUrl(""))
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -180,7 +183,8 @@ public sealed class SignatureVerificationCheck : VerificationCheckBase
|
||||
"Network connectivity issue")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Test Rekor", $"curl -I {rekorHealthUrl}")
|
||||
.AddManualStep(2, "Or use offline mode", "Configure offline verification bundle"))
|
||||
.AddManualStep(2, "Or use offline mode", "Configure offline verification bundle")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.signature")
|
||||
.Build();
|
||||
}
|
||||
@@ -208,7 +212,8 @@ public sealed class SignatureVerificationCheck : VerificationCheckBase
|
||||
.WithCauses("Network connectivity issue")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Check network", "Verify connectivity to Rekor")
|
||||
.AddManualStep(2, "Use offline mode", "Configure offline verification bundle"))
|
||||
.AddManualStep(2, "Use offline mode", "Configure offline verification bundle")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.signature")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -78,7 +78,8 @@ public sealed class TestArtifactPullCheck : VerificationCheckBase
|
||||
"Path is incorrect")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Verify file exists", $"ls -la {bundlePath}")
|
||||
.AddShellStep(2, "Export bundle from online system", "stella verification bundle export --output " + bundlePath))
|
||||
.AddShellStep(2, "Export bundle from online system", "stella verification bundle export --output " + bundlePath)
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.artifact.pull")
|
||||
.Build());
|
||||
}
|
||||
@@ -113,7 +114,8 @@ public sealed class TestArtifactPullCheck : VerificationCheckBase
|
||||
.Add("Error", "Could not parse registry and repository"))
|
||||
.WithCauses("Reference format is incorrect")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Fix reference format", "Use format: oci://registry/repository@sha256:digest or registry/repository@sha256:digest"))
|
||||
.AddManualStep(1, "Fix reference format", "Use format: oci://registry/repository@sha256:digest or registry/repository@sha256:digest")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.artifact.pull")
|
||||
.Build();
|
||||
}
|
||||
@@ -151,7 +153,8 @@ public sealed class TestArtifactPullCheck : VerificationCheckBase
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Test with crane", $"crane manifest {reference}")
|
||||
.AddManualStep(2, "Check registry credentials", "Ensure registry credentials are configured")
|
||||
.AddManualStep(3, "Verify artifact exists", "Confirm the test artifact has been pushed to the registry"))
|
||||
.AddManualStep(3, "Verify artifact exists", "Confirm the test artifact has been pushed to the registry")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.artifact.pull")
|
||||
.Build();
|
||||
}
|
||||
@@ -178,7 +181,8 @@ public sealed class TestArtifactPullCheck : VerificationCheckBase
|
||||
"Wrong artifact tag being pulled")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Update expected digest", $"Set Doctor:Plugins:Verification:TestArtifact:ExpectedDigest to {responseDigest}")
|
||||
.AddManualStep(2, "Or use digest in reference", "Use @sha256:... in the reference instead of :tag"))
|
||||
.AddManualStep(2, "Or use digest in reference", "Use @sha256:... in the reference instead of :tag")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.artifact.pull")
|
||||
.Build();
|
||||
}
|
||||
@@ -208,7 +212,8 @@ public sealed class TestArtifactPullCheck : VerificationCheckBase
|
||||
"DNS resolution failure")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Test registry connectivity", $"curl -I https://{registry}/v2/")
|
||||
.AddManualStep(2, "Check network configuration", "Ensure HTTPS traffic to the registry is allowed"))
|
||||
.AddManualStep(2, "Check network configuration", "Ensure HTTPS traffic to the registry is allowed")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.artifact.pull")
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -76,7 +76,8 @@ public sealed class VexValidationCheck : VerificationCheckBase
|
||||
.Add("BundlePath", bundlePath)
|
||||
.Add("FileExists", "false"))
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Export bundle", "stella verification bundle export --include-vex --output " + bundlePath))
|
||||
.AddShellStep(1, "Export bundle", "stella verification bundle export --include-vex --output " + bundlePath)
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.vex.validation")
|
||||
.Build());
|
||||
}
|
||||
@@ -103,7 +104,8 @@ public sealed class VexValidationCheck : VerificationCheckBase
|
||||
"Test artifact has no known vulnerabilities")
|
||||
.WithRemediation(r => r
|
||||
.AddShellStep(1, "Re-export with VEX", "stella verification bundle export --include-vex --output " + bundlePath)
|
||||
.AddManualStep(2, "This may be expected", "VEX documents are only needed when vulnerabilities exist"))
|
||||
.AddManualStep(2, "This may be expected", "VEX documents are only needed when vulnerabilities exist")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.vex.validation")
|
||||
.Build());
|
||||
}
|
||||
@@ -154,7 +156,8 @@ public sealed class VexValidationCheck : VerificationCheckBase
|
||||
.Add("Note", "VEX collection is optional but recommended for vulnerability context"))
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Enable VEX collection", "Set VexHub:Collection:Enabled to true")
|
||||
.AddManualStep(2, "Configure VEX feeds", "Add vendor VEX feeds to VexHub:Feeds"))
|
||||
.AddManualStep(2, "Configure VEX feeds", "Add vendor VEX feeds to VexHub:Feeds")
|
||||
.WithRunbookUrl(""))
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -170,7 +173,8 @@ public sealed class VexValidationCheck : VerificationCheckBase
|
||||
.Add("Note", "VEX feeds provide vendor vulnerability context"))
|
||||
.WithCauses("No VEX feed URLs configured")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Configure VEX feeds", "Add vendor VEX feeds to VexHub:Feeds array"))
|
||||
.AddManualStep(1, "Configure VEX feeds", "Add vendor VEX feeds to VexHub:Feeds array")
|
||||
.WithRunbookUrl(""))
|
||||
.WithVerification($"stella doctor --check check.verification.vex.validation")
|
||||
.Build());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user