Files

96 lines
4.2 KiB
C#

using System.Reflection;
using System.Text.Json;
namespace ZB.MOM.WW.ScadaBridge.Host.Tests;
/// <summary>
/// Host-003 regression: secrets must not be committed in plaintext in the
/// shipped <c>appsettings.Central.json</c>. 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.
/// </summary>
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", "ZB.MOM.WW.ScadaBridge.Host");
if (Directory.Exists(hostPath))
return hostPath;
dir = dir.Parent;
}
throw new DirectoryNotFoundException("Could not locate src/ZB.MOM.WW.ScadaBridge.Host");
}
private static JsonElement ScadaBridgeSection()
{
var path = Path.Combine(FindHostProjectDirectory(), "appsettings.Central.json");
var json = File.ReadAllText(path);
using var doc = JsonDocument.Parse(json);
return doc.RootElement.GetProperty("ScadaBridge").Clone();
}
[Fact]
public void CentralConfig_ConnectionStrings_ContainNoPlaintextPassword()
{
var db = ScadaBridgeSection().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()
{
// Task 1.4 cutover: the LDAP service-account password moved out of the flat
// Security:LdapServiceAccountPassword key into the nested Security:Ldap
// sub-section (Security:Ldap:ServiceAccountPassword), bound to the shared
// ZB.MOM.WW.Auth LdapOptions. Walk into Security:Ldap and guard the nested
// key — checking the deleted flat key would pass vacuously.
var security = ScadaBridgeSection().GetProperty("Security");
var ldap = security.GetProperty("Ldap");
if (ldap.TryGetProperty("ServiceAccountPassword", out var pw))
{
var value = pw.GetString() ?? string.Empty;
Assert.True(
value.Length == 0 || value.Contains('{') || value.Contains('$'),
$"appsettings.Central.json carries a plaintext Security:Ldap:ServiceAccountPassword '{value}'. " +
"Move it to an environment variable (ScadaBridge__Security__Ldap__ServiceAccountPassword).");
}
}
[Fact]
public void CentralConfig_JwtSigningKey_IsNotCommitted()
{
var security = ScadaBridgeSection().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.");
}
}
}