sqlinsight-pro/db/permissions/grant-least-privilege.sql
Søren Eggert Lundsteen Olsen 4ba7e5195e Privilege audit: sysadmin and server roles with AD unfolding
Collects members of the fixed server roles and CONTROL SERVER every 15
minutes with login type, disabled state and SQL login password policy.
Windows groups are unfolded recursively through Active Directory on a
Windows agent (cached, 20 s timeout, failures shown with the reason).
Changes since the previous collection are logged, and who/when is read
from the default trace (event 108) when the agent has the optional
ALTER TRACE; authors that only reach the buffered trace later are filled
in at the next collection. The first collection is the baseline.

New rules SQLI-SEC-001 (new privilege, High for sysadmin/securityadmin/
CONTROL SERVER) and SQLI-SEC-002 (more sysadmins than the limit, default
3). Names are sensitive evidence and never in titles or subjects.

Web: /instanser/{navn}/rettigheder and /rettigheder with change log by
period, alert banner on the instance page and overview, and an audit
PDF (/rapporter/rettigheder.pdf) linked from Rapporter.

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

118 lines
5.2 KiB
Transact-SQL

/*
SQLInsight Pro: least-privilege rettigheder til agentens login
================================================================
Giver agenten præcis de rettigheder, den skal bruge for at overvåge instansen. Aldrig sysadmin.
Scriptet kan køres igen uden skade, fx efter en opgradering eller når der er oprettet nye databaser.
Understøtter SQL Server 2005 og nyere. SQL Server 2000 har ikke VIEW SERVER STATE: se docs/legacy-versions.md.
Forudsætning: login'et findes allerede, fx
CREATE LOGIN [DOMÆNE\svc-sqlinsight$] FROM WINDOWS; -- gMSA (anbefalet)
CREATE LOGIN [sqlinsight-agent] FROM EXTERNAL PROVIDER; -- Azure SQL Managed Instance (Entra ID)
Hvad der gives, og hvorfor:
Server
2005-2019: VIEW SERVER STATE DMV'er: waits, sessions, plan cache, I/O
2022+: VIEW SERVER PERFORMANCE STATE samme, mere granulært
VIEW SERVER SECURITY STATE sikkerheds-DMV'er (fx audit, certifikater)
Alle: VIEW ANY DEFINITION metadata (indexes, statistikker). Giver IKKE adgang til data.
2014+: CONNECT ANY DATABASE kan åbne alle databaser (nuværende og fremtidige). Giver IKKE adgang til data.
Databaser (kun før 2014): en bruger uden rettigheder i hver online database, så DMV'er og Query Store kan læses.
msdb: SELECT på backup-, restore-, job- og suspect_pages-tabeller. Intet andet.
Hvad der IKKE gives: adgang til tabeldata, skriverettigheder, xp_cmdshell eller sysadmin.
*/
SET NOCOUNT ON;
DECLARE @LoginName sysname;
SET @LoginName = N'DOMÆNE\svc-sqlinsight$'; -- <== ret til agentens login
DECLARE @Major int;
DECLARE @Sql nvarchar(max);
DECLARE @Quoted nvarchar(300);
SET @Major = CAST(PARSENAME(CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)), 4) AS int);
/* Azure SQL Managed Instance reports 12.x but runs the newest engine. Use the broad, universally supported permissions. */
IF CAST(SERVERPROPERTY('EngineEdition') AS int) = 8 SET @Major = 15;
SET @Quoted = QUOTENAME(@LoginName);
IF @Major < 9
BEGIN
RAISERROR(N'SQL Server 2000 understøttes ikke af dette script. Se docs/legacy-versions.md.', 16, 1);
RETURN;
END;
IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = @LoginName)
BEGIN
RAISERROR(N'Login %s findes ikke. Opret det først.', 16, 1, @LoginName);
RETURN;
END;
IF IS_SRVROLEMEMBER(N'sysadmin', @LoginName) = 1
PRINT N'ADVARSEL: ' + @LoginName + N' er sysadmin. Agenten har ikke brug for det; fjern rollen.';
/* Server-rettigheder */
IF @Major >= 16
SET @Sql = N'GRANT VIEW SERVER PERFORMANCE STATE TO ' + @Quoted + N'; GRANT VIEW SERVER SECURITY STATE TO ' + @Quoted + N';';
ELSE
SET @Sql = N'GRANT VIEW SERVER STATE TO ' + @Quoted + N';';
EXEC (@Sql);
SET @Sql = N'GRANT VIEW ANY DEFINITION TO ' + @Quoted + N';';
EXEC (@Sql);
IF @Major >= 12
BEGIN
SET @Sql = N'GRANT CONNECT ANY DATABASE TO ' + @Quoted + N';';
EXEC (@Sql);
END
ELSE
BEGIN
/* Før 2014: en bruger uden rettigheder i hver online database. Kør scriptet igen, når der oprettes nye. */
DECLARE @Db sysname;
DECLARE db_cursor CURSOR LOCAL FAST_FORWARD FOR
SELECT name FROM sys.databases
WHERE state = 0 AND name NOT IN (N'master', N'tempdb', N'model', N'msdb') AND is_read_only = 0;
OPEN db_cursor;
FETCH NEXT FROM db_cursor INTO @Db;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @Sql = N'USE ' + QUOTENAME(@Db) + N';
IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE sid = SUSER_SID(' + QUOTENAME(@LoginName, '''') + N'))
CREATE USER ' + @Quoted + N' FOR LOGIN ' + @Quoted + N';';
EXEC (@Sql);
FETCH NEXT FROM db_cursor INTO @Db;
END;
CLOSE db_cursor;
DEALLOCATE db_cursor;
END;
/* msdb: backup-historik, jobs og suspect pages */
SET @Sql = N'USE msdb;
IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE sid = SUSER_SID(' + QUOTENAME(@LoginName, '''') + N'))
CREATE USER ' + @Quoted + N' FOR LOGIN ' + @Quoted + N';
GRANT SELECT ON dbo.backupset TO ' + @Quoted + N';
GRANT SELECT ON dbo.backupmediafamily TO ' + @Quoted + N';
GRANT SELECT ON dbo.backupmediaset TO ' + @Quoted + N';
GRANT SELECT ON dbo.restorehistory TO ' + @Quoted + N';
GRANT SELECT ON dbo.suspect_pages TO ' + @Quoted + N';
GRANT SELECT ON dbo.sysjobs TO ' + @Quoted + N';
GRANT SELECT ON dbo.sysjobsteps TO ' + @Quoted + N';
GRANT SELECT ON dbo.sysjobhistory TO ' + @Quoted + N';
GRANT SELECT ON dbo.sysjobactivity TO ' + @Quoted + N';
GRANT SELECT ON dbo.sysjobschedules TO ' + @Quoted + N';
GRANT SELECT ON dbo.sysschedules TO ' + @Quoted + N';
GRANT SELECT ON dbo.syscategories TO ' + @Quoted + N';';
EXEC (@Sql);
/*
Valgfrit: hvem og hvornår ved ændringer af serverroller.
Rettighedsrevisionen (sysadmin m.m.) virker uden. Med ALTER TRACE kan agenten desuden læse default trace og vise,
HVEM der gav en ny rettighed og HVORNÅR. ALTER TRACE giver også adgang til at oprette traces, så det er et bevidst
valg; fjern kommentaren for at give den:
SET @Sql = N'GRANT ALTER TRACE TO ' + @Quoted + N';';
EXEC (@Sql);
*/
PRINT N'Rettigheder givet til ' + @LoginName + N'.';