On a distributor, a 15-minute collector reads each distribution agent's
latest run (status, latency, message) and replication errors the last
day from the distribution database. That needs SELECT on
MSdistribution_agents, MSdistribution_history and MSrepl_errors, which
the agent does not have by default (never the replicated commands);
without it SQLI-REPL-001 (agent failed/retrying, High) and SQLI-REPL-002
(latency > 5 min, Medium) are "not measurable" with the exact grant, and
"not relevant" elsewhere. Shown on the instance page and in
GET /api/v1/instances/{name}/replication. Tested against a real
distributor set up in the SQL 2022 container.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
38 lines
2.2 KiB
C#
38 lines
2.2 KiB
C#
using SQLInsight.Collectors.Instance;
|
|
using SQLInsight.Core.Data;
|
|
|
|
namespace SQLInsight.IntegrationTests;
|
|
|
|
/// <summary>Replication on a distributor, as the least-privilege agent: not readable without rights, readable with SELECT on the agent tables.</summary>
|
|
[Collection(SqlServerTestGroup.Name)]
|
|
[Trait("Category", "Integration")]
|
|
public class ReplicationTests(SqlServerFixture sql)
|
|
{
|
|
[Fact]
|
|
public async Task A_distributor_is_unreadable_without_rights_and_readable_with_select_on_the_agent_tables()
|
|
{
|
|
await using var session = await AgentSession.OpenAsync(sql);
|
|
var before = await session.CollectAsync<ReplicationSnapshot>(new ReplicationCollector());
|
|
Assert.False(before.IsDistributor);
|
|
|
|
await sql.ExecuteAsSaAsync("DECLARE @s sysname; SET @s = @@SERVERNAME; EXEC sp_adddistributor @distributor = @s, @password = N'Distr-Passw0rd!';");
|
|
await sql.ExecuteAsSaAsync("EXEC sp_adddistributiondb @database = N'distribution';");
|
|
try
|
|
{
|
|
var denied = await session.CollectAsync<ReplicationSnapshot>(new ReplicationCollector());
|
|
await sql.ExecuteAsSaAsync($"USE distribution; IF USER_ID(N'{SqlServerFixture.AgentLogin}') IS NULL CREATE USER [{SqlServerFixture.AgentLogin}] FOR LOGIN [{SqlServerFixture.AgentLogin}]; GRANT SELECT ON dbo.MSdistribution_agents TO [{SqlServerFixture.AgentLogin}]; GRANT SELECT ON dbo.MSdistribution_history TO [{SqlServerFixture.AgentLogin}]; GRANT SELECT ON dbo.MSrepl_errors TO [{SqlServerFixture.AgentLogin}];");
|
|
var readable = await session.CollectAsync<ReplicationSnapshot>(new ReplicationCollector());
|
|
|
|
Assert.True(denied.IsDistributor);
|
|
Assert.Contains("GRANT SELECT ON dbo.MSdistribution_agents", denied.Unavailable, StringComparison.Ordinal);
|
|
Assert.True(readable.Unavailable is null, readable.Unavailable);
|
|
Assert.Empty(readable.Agents);
|
|
Assert.Equal(0, readable.ErrorsLastDay);
|
|
}
|
|
finally
|
|
{
|
|
await sql.ExecuteAsSaAsync("EXEC sp_dropdistributiondb @database = N'distribution';");
|
|
await sql.ExecuteAsSaAsync("EXEC sp_dropdistributor @no_checks = 1, @ignore_distributor = 1;");
|
|
}
|
|
}
|
|
}
|