using System.Reflection; using System.Text.Json; namespace ScadaLink.Host.Tests; /// /// Host-003 regression: secrets must not be committed in plaintext in the /// shipped appsettings.Central.json. Connection-string passwords, the LDAP /// service-account password and the JWT signing key must be supplied via /// environment variables (or another secret store) at deployment time — the /// committed file may only carry non-sensitive structural defaults or /// placeholder values. /// public class ConfigSecretsTests { private static string FindHostProjectDirectory() { var assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; var dir = new DirectoryInfo(assemblyDir); while (dir != null) { var hostPath = Path.Combine(dir.FullName, "src", "ScadaLink.Host"); if (Directory.Exists(hostPath)) return hostPath; dir = dir.Parent; } throw new DirectoryNotFoundException("Could not locate src/ScadaLink.Host"); } private static JsonElement ScadaLinkSection() { var path = Path.Combine(FindHostProjectDirectory(), "appsettings.Central.json"); var json = File.ReadAllText(path); using var doc = JsonDocument.Parse(json); return doc.RootElement.GetProperty("ScadaLink").Clone(); } [Fact] public void CentralConfig_ConnectionStrings_ContainNoPlaintextPassword() { var db = ScadaLinkSection().GetProperty("Database"); foreach (var prop in db.EnumerateObject()) { var value = prop.Value.GetString() ?? string.Empty; // A committed connection string must not carry a literal Password= value. // Either the password is delivered via an environment variable or the // whole connection string is. A placeholder reference is acceptable. var idx = value.IndexOf("Password=", StringComparison.OrdinalIgnoreCase); if (idx >= 0) { var after = value[(idx + "Password=".Length)..]; var literal = after.Split(';')[0]; Assert.True( literal.Length == 0 || literal.Contains('{') || literal.Contains('$'), $"appsettings.Central.json '{prop.Name}' contains a plaintext Password value '{literal}'. " + "Move the secret to an environment variable."); } } } [Fact] public void CentralConfig_LdapServiceAccountPassword_IsNotCommitted() { var security = ScadaLinkSection().GetProperty("Security"); if (security.TryGetProperty("LdapServiceAccountPassword", out var pw)) { var value = pw.GetString() ?? string.Empty; Assert.True( value.Length == 0 || value.Contains('{') || value.Contains('$'), $"appsettings.Central.json carries a plaintext LdapServiceAccountPassword '{value}'. " + "Move it to an environment variable."); } } [Fact] public void CentralConfig_JwtSigningKey_IsNotCommitted() { var security = ScadaLinkSection().GetProperty("Security"); if (security.TryGetProperty("JwtSigningKey", out var key)) { var value = key.GetString() ?? string.Empty; Assert.True( value.Length == 0 || value.Contains('{') || value.Contains('$'), $"appsettings.Central.json carries a committed JwtSigningKey '{value}'. " + "A committed signing key lets anyone with repo access forge session tokens. " + "Move it to an environment variable."); } } }