sqlinsight-pro/tests/SQLInsight.Core.Tests/InstanceConfigurationTests.cs
Søren Eggert Lundsteen Olsen 8776faefb3 Instance configuration core: database selection, sealed credentials, agent request contracts
DatabaseSelection decides which databases the database-specific checks run in
(system databases always), and which may send Dynamics AX findings to the hub
(default none). CredentialEnvelope seals a SQL login password with the agent's
RSA public key (RSA-OAEP-SHA256 + AES-256-GCM), bound to server and login, so only
the agent can open it. Shared contracts for connection tests and discovery, server
address validation, the service account login rule, and the least-privilege GRANT
script filled in for one login.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:21:59 +02:00

164 lines
7.1 KiB
C#

using System.Security.Cryptography;
using SQLInsight.Core.Configuration;
namespace SQLInsight.Core.Tests;
public sealed class DatabaseSelectionTests
{
[Fact]
public void All_monitors_every_database_and_sends_no_dynamics_ax_findings()
{
Assert.True(DatabaseSelection.All.IsEverything);
Assert.True(DatabaseSelection.All.Includes("Sales"));
Assert.False(DatabaseSelection.All.SendsDynamicsAxToHub("AX30"));
Assert.False(DatabaseSelection.All.AnyDynamicsAxToHub);
}
[Fact]
public void Ticked_databases_are_monitored_unticked_are_not_and_new_ones_follow_the_instance_setting()
{
var selection = new DatabaseSelection(monitorNew: false, ["AX30"], ["Archive"], []);
Assert.True(selection.Includes("ax30"));
Assert.False(selection.Includes("Archive"));
Assert.False(selection.Includes("NewDb"));
Assert.True(new DatabaseSelection(monitorNew: true, ["AX30"], ["Archive"], []).Includes("NewDb"));
Assert.Equal(["AX30"], selection.Filter(["AX30", "Archive", "NewDb"]));
}
[Theory]
[InlineData("master")]
[InlineData("MSDB")]
[InlineData("model")]
[InlineData("tempdb")]
public void System_databases_are_always_monitored(string database)
{
Assert.True(new DatabaseSelection(false, [], [database], []).Includes(database));
}
[Fact]
public void Dynamics_ax_findings_go_to_the_hub_only_for_opted_in_monitored_databases()
{
var selection = new DatabaseSelection(false, ["AX30", "AX40"], ["AXOLD"], ["AX30", "AXOLD"]);
Assert.True(selection.SendsDynamicsAxToHub("AX30"));
Assert.False(selection.SendsDynamicsAxToHub("AX40"));
Assert.False(selection.SendsDynamicsAxToHub("AXOLD")); // opted in, but not monitored
Assert.False(selection.SendsDynamicsAxToHub(null));
Assert.True(selection.AnyDynamicsAxToHub);
}
[Theory]
[InlineData("COM-AX-001", true)]
[InlineData("com-ax-012", true)]
[InlineData("COM-FT-001", false)]
[InlineData("SQLI-DB-001", false)]
public void Dynamics_ax_rules_are_recognised_by_their_id(string ruleId, bool expected) =>
Assert.Equal(expected, DynamicsAxFindings.IsDynamicsAxRule(ruleId));
[Theory]
[InlineData("database: AX30", "AX30")]
[InlineData("database: ", null)]
[InlineData("ag-database: g/r/AX30", null)]
[InlineData(null, null)]
public void Database_is_read_from_the_subject(string? subject, string? expected) =>
Assert.Equal(expected, DynamicsAxFindings.DatabaseOf(subject));
}
public sealed class CredentialEnvelopeTests
{
private static (RSA Key, byte[] PublicKey) NewKey()
{
var rsa = RSA.Create(3072);
return (rsa, rsa.ExportSubjectPublicKeyInfo());
}
[Fact]
public void Sealed_password_opens_only_with_the_agent_key_and_the_same_server_and_login()
{
var (key, publicKey) = NewKey();
using var _ = key;
var context = CredentialEnvelope.Context("sql01\\PROD,1433", "sqlinsight");
var envelope = CredentialEnvelope.Seal("Hemmelig-1!", "agent-1", publicKey, context);
Assert.Equal("agent-1", CredentialEnvelope.KeyIdOf(envelope));
Assert.DoesNotContain("Hemmelig", System.Text.Encoding.UTF8.GetString(envelope), StringComparison.Ordinal);
Assert.Equal("Hemmelig-1!", CredentialEnvelope.Open(envelope, "agent-1", key, CredentialEnvelope.Context("SQL01\\prod,1433", "SQLINSIGHT")));
Assert.Throws<CredentialException>(() => CredentialEnvelope.Open(envelope, "agent-1", key, CredentialEnvelope.Context("evil01", "sqlinsight")));
Assert.Throws<CredentialException>(() => CredentialEnvelope.Open(envelope, "agent-1", key, CredentialEnvelope.Context("sql01\\PROD,1433", "sa")));
Assert.Throws<CredentialException>(() => CredentialEnvelope.Open(envelope, "agent-2", key, context));
}
[Fact]
public void Another_agent_key_or_a_tampered_envelope_cannot_be_opened()
{
var (key, publicKey) = NewKey();
var (other, _) = NewKey();
using var a = key;
using var b = other;
var context = CredentialEnvelope.Context("sql01", "u");
var envelope = CredentialEnvelope.Seal("pw", "k", publicKey, context);
Assert.Throws<CredentialException>(() => CredentialEnvelope.Open(envelope, "k", other, context));
envelope[^1] ^= 0xFF;
Assert.Throws<CredentialException>(() => CredentialEnvelope.Open(envelope, "k", key, context));
Assert.Null(CredentialEnvelope.KeyIdOf([9, 9, 9]));
}
}
public sealed class ServerAddressTests
{
[Theory]
[InlineData("sql01", null, "sql01")]
[InlineData(" sql01\\PROD ", null, "sql01\\PROD")]
[InlineData("sql01.firma.local\\PROD", 1450, "sql01.firma.local\\PROD,1450")]
public void Data_source_adds_the_port(string server, int? port, string expected) =>
Assert.Equal(expected, ServerAddress.DataSource(server, port));
[Theory]
[InlineData("sql01", null, true)]
[InlineData("sql01\\PROD", 1433, true)]
[InlineData("tcp:sql01.firma.local", null, true)]
[InlineData("", null, false)]
[InlineData("sql01;Integrated Security=false", null, false)]
[InlineData("sql01,1433", null, false)]
[InlineData("sql01\\a\\b", null, false)]
[InlineData("sql01", 0, false)]
[InlineData("sql01", 70000, false)]
public void Server_and_port_are_validated(string server, int? port, bool valid) =>
Assert.Equal(valid, ServerAddress.Validate(server, port) is null);
[Theory]
[InlineData(@"NT SERVICE\SQLInsight Agent", "sql01", "AGENT01", "FIRMA", @"FIRMA\AGENT01$")]
[InlineData(@"NT SERVICE\SQLInsight Agent", "agent01\\PROD", "AGENT01", "FIRMA", @"NT SERVICE\SQLInsight Agent")]
[InlineData(@"FIRMA\svc-sqlinsight$", "sql01", "AGENT01", "FIRMA", @"FIRMA\svc-sqlinsight$")]
[InlineData("LocalSystem", "sql01", "AGENT01", "FIRMA", @"FIRMA\AGENT01$")]
public void Service_account_login_on_the_server(string account, string server, string machine, string domain, string expected) =>
Assert.Equal(expected, ServiceAccounts.LoginFor(account, server, machine, domain));
}
public sealed class GrantScriptTests
{
[Fact]
public void Script_is_filled_in_for_the_login_and_creates_a_missing_windows_login()
{
var script = GrantScript.For(@"FIRMA\AGENT01$", windowsLogin: true);
Assert.Contains(@"SET @LoginName = N'FIRMA\AGENT01$';", script, StringComparison.Ordinal);
Assert.DoesNotContain("SET @LoginName = N'DOMÆNE", script, StringComparison.Ordinal);
Assert.Contains(@"CREATE LOGIN [FIRMA\AGENT01$] FROM WINDOWS;", script, StringComparison.Ordinal);
Assert.Contains("VIEW SERVER STATE", script, StringComparison.Ordinal);
Assert.DoesNotContain("PXI_BATCHMONITORTABLE", script, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void Quotes_in_a_login_are_escaped()
{
var script = GrantScript.For("o'brien]", windowsLogin: false);
Assert.Contains("SET @LoginName = N'o''brien]';", script, StringComparison.Ordinal);
Assert.Contains("CREATE LOGIN [o'brien]]]", script, StringComparison.Ordinal);
Assert.DoesNotContain("IF SUSER_ID(", script, StringComparison.Ordinal);
}
}