This is the continuation of my series on auditing SQL Server. In the first part, I discussed basic server discovery and documentation. The next section went over server hardware configuration and audits. This section gets into detailed SQL Server configuration details. This is less relevant in a cloud environment, but there are still items to confirm.
The format follows the server audit format. A general template is presented first, followed by interpreting those configuration items and ends with methods to explore and list those configurations.
General Audit Template
- SQL Server configuration
- Edition and patch level
- Default file locations
- Data
- Log
- Backup
- tempdb
- SQL Server configuration items and properties
- Logon triggers
- Running traces
- Extended event sessions
- Login model and server roles
- SQL Agent jobs and Elastic jobs
- Replication
- Clustering and always-on
- Service accounts
- SQL Server audits
- Query store
- Linked servers
- Backups
- Environment differences
- SQL Server configuration
Evaluating SQL Server Configuration
SQL Server configuration audit is similar to the OS evaluation with some additional details. This is the high-level check of your SQL system and it won’t apply if you are in an Azure managed instance. You need to verify the edition and patches, the backups, file locations, and other setup options.
Edition and patch level
The first, most basic check is the SQL edition and patch level. Like the OS, you want to be sure the edition is supported and the patch level is within enterprise guidelines. This is a very critical item. Security, performance, and upgrade paths are all directly impacted by the edition.
Document this and recommend an upgrade if it is getting out-of-date. Be sure it is a currently supported version. I’d also check the life cycle to ensure there is an upgrade plan before end-of-life for that version.
Default file locations
Default file location at a server level is primarily informational only. The settings that need to be interrogated thoroughly are the actual database file locations. Setting the defaults at a server level makes it more likely that individual databases will be setup correctly as they are added. The data, log, and backups should each be on separate drives. Tempdb isn’t listed with the defaults, but it should also be on a separate drive. Document the default locations and flag them if they are on same physical drive. In a cloud environment this isn’t an item that needs to be validated, and in fact, can’t be modified.
SQL Server Configuration and Properties
SQL Server has many configuration settings. My rule of thumb is to not change settings unless there is a specific reason. When looking at sever settings, pay attention to the items that are not using the default values. The primary exception for this is memory configuration. You will want to set the minimum and maximum memory for SQL Server. This can prevent memory contention with the underlying operating system.
Many of these items are important, but most shouldn’t be modified unless there is a specific, documented, reason. The main exception to this is “min server memory (MB)” and “max server memory (MB)”. It is a best practice to set the min and max to the same value. As discussed in the discovery audit, enough memory should be left for the operating system to function efficiently. The recommended value will depend on the current version of the operating system, usually be 8-12 GB.
Any other value changed from the default needs to be noted as part of the audit. There should be a reason for the change, and that reason should make sense. If the owner or the administrators of the server have changed, it may be worth investigating if the value should be reverted to the default. If you do this – be ready to quickly change it back. Each server and the associated application has a personality (SQL Server usage pattern), and hopefully the value was changed due to careful testing and planning.
New properties are added as the editions progress, so be sure to look for new values with each new version. Looking at the properties available, you can see that many don’t impact performance, but they are all useful.
Logon triggers
Logon triggers should be used sparingly and with purpose. Most servers won’t have them, so if they are present this is another item that needs to be documented and explained. They have the potential to severely alter logon behavior and reduce logon performance for the entire server. It is also possible to have multiple logon triggers. Document all logon triggers, note their purpose and document them in your audit.
You will want to work with the administrators to understand why they are present. I’m not a big fan of any trigger. They can be like an unwanted easter egg that you discover when something isn’t working as expected. They are fine if they are documented and are carefully coded. But use them with caution and make sure developers understand their behavior.
Running traces and extended events
For full SQL Server installations, you can still use traces. They will be deprecated in a future version, and Microsoft recommends moving to extended events. With that caveat, traces are still a very useful tool, commonly used. It’s good to know if any traces are running. This can give you an idea of concerns that admins have had with the server and items you may want to dig into further. It is also an area to target for potential upgrades. They don’t run on Azure SQL Database.
Extended events were created to (eventually) replace traces, along with other Azure functionality, such as audits. As with traces, it’s good to know if extended events are configured and what they are doing. Document both of these and note the events getting logged.
Login model and server roles
The login model determines if only Windows authentication can be used or SQL logins can be used in addition to Windows authentication, or various flavors of Entra IDs in Azure. In the past, the best practice was to use Windows / Integrated Authentication exclusively, when it was possible. In Azure Database, that was not an option until recently. Document the login model for the server. The team should understand the model they are using and follow best practices for that model. Managed identity is also available in Azure SQL and is considered a best practice for service accounts for other Azure resources.
The server roles granted to logins should be checked. There shouldn’t be many logins with server roles or server security directly assigned. This is especially true for the sysadmin role and control authorization at the server level. This is a critical item. List accounts with server roles and review this list with the server owners / stakeholders to be sure it is correct.
SQL Agent jobs and Elastic Jobs
On-prem servers will generally use SQL Agent jobs for maintenance tasks and other recurring database tasks. Document the jobs and look at the error logs to be sure they are running correctly. Talk with the DBA team to ensure these logs are regularly checked.
SQL Agent isn’t available in Azure SQL, but Elastic Jobs are now out of preview and can be used without concern they will be going away soon. Automated deployments, ETL, or anything else that can run TSQL can be used to run maintenance jobs in Azure, but Elastic Jobs seem like the obvious replacement.
These jobs will often run maintenance plans for on-prem servers. That will cover backups, index and statistic maintenance, and database consistency checks. In an Azure environment, you still need to maintain indexes and statistics and perform consistency checks. This can be done via Elastic jobs or other automated methods.
If you dig a little deeper into these jobs, you will want to be sure credentials aren’t stored in plain text. I have seen this multiple times at various enterprises, so be sure to check for this. Document the jobs running and their schedules. You also want to document the authentication method and accounts.
Replication
Replication can be used for a number of purposes in an enterprise, including reporting and distributed database scenarios. It is useful to know if replication is configured and the model used. This can help illuminate the security landscape and is an indication of other servers to investigate. There are also administrative tasks associated with replication. Document if the server is used as a publisher, distributor, or subscriber, administration tasks performed, and the other servers involved in replication.
Clustering and Always On
Critical servers should have some type of high-availability option configured. It might be clustering or Always On availability groups. This isn’t required, but usage should reflect the business need. If an application is deemed critical, you will want to see that reflected at the database level. Document the high-availability options used. If the server or application is listed as critical, but it doesn’t have a high-availability option configured, that is a critical item. Help the stakeholder understand the risk with a single point of failure.
Service accounts
The service accounts used for the SQL services should be documented. Remember that these accounts should follow the practice of least privilege and have the lowest possible rights to work. Refer to the Microsoft documentation linked at the bottom for specifics.
This is a critical item. The accounts should have the lowest possible privileges, at both a domain level and a machine level, while still able to run the services. I’ve seen DBAs confused by this concept, but it’s extremely critical. At a more pessimistic level, I’ve also seen elevated privileges used simply because it’s easier and less time consuming. If a bad actor gets access to the machine, they can leverage these accounts to expand access in the network. Document the service accounts and the general level of access for each of these accounts at both the machine level and network level. I would consider machine level admins a critical security item and elevated domain privileges are even more concerning.
SQL Server Audits
SQL Server audits can be used to track security related items in Azure environments. They replace some of the functionality in on-prem traces. Audits can be used to track query usage and security issues. They are not required, but if used, document their usage and the specific events logged.
Query store
Usage of the query store isn’t an issue, just an item to be noted. You may want to dig into it further and look at the queries and query plans getting stored. It can be an opportunity to adjust the data model or add indexes. Document the usage and discuss any reasons why it was configured to see if there are performance problems that need to be explored further.
Linked servers
Linked servers are another item that aren’t critical, but can have security implications and can impact cloud migrations. Linked servers aren’t supported with Azure databases, so that functionality would need to be changed to a different technology in migration scenarios. It is also possible to get elevated security to databases via linked servers. Document the linked servers and the security model used by any configured on the server.
Elastic queries are still in preview, but they have been available for several years. They can mimic some of the behavior of linked servers in Azure. Document the usage of elastic queries and work with the development teams to understand their usage.
Backups
Next, ensure that backups are happening. There may be different backup types for the various databases on a server, but backups should be configured and they should be correct for each database. This is a critical item and should be flagged if they are not happening. Understand the backup location, restore process, off-site storage and testing procedures. This is possibly the most critical item on the server and warrants thorough investigation. Technical server owners and possibly business stakeholders should be notified immediately if you confirm that backups are not configured or badly misconfigured.
Evaluating SQL Server Environment Differences
When there are multiple environments for a project, application, or set of databases, the environments at a server and SQL Server level will generally be different. The database level items should be minimal or even non-existent.
The SQL Server configuration should be very close or identical between environments. There are fewer reasons for these configurations to differ. Database jobs will differ, there may be different accounts used between environments, and different logons and authorization (different accounts for non-prod / prod, different access for developers in non-prod / prod, etc.), but the configuration items should generally match.
Performance settings, SQL Server configuration options, server and patch levels should match. Note any differences in your audit.
Gathering the SQL Audit Information
To adequately examine the SQL Server configuration requires advanced privileges. Work with your DBA team as needed.
- Edition and patch level
- Default file locations
- Configuration items not set to default
- Additional server settings
- Logon triggers
- Default traces
- Extended event sessions
- Login model and settings
- Replication
- Service accounts
- Query store
SQL Server Edition and Patch Level
One of the easiest and most important items to check is the server version. You will get different information back depending on the type of server deployment. I showed this in the server audit, but I’ll show it again here.
1 |
SELECT @@VERSION |
This will show the current edition and patch level.
Default file locations
The following works with SQL Server 2019 and higher. For previous versions, use SSMS or interrogate the registry for the default backup path location.
1 2 3 4 |
SELECT SERVERPROPERTY('InstanceDefaultDataPath') DefaultDataPath ,SERVERPROPERTY('InstanceDefaultLogPath') DefaultLogPath ,SERVERPROPERTY('InstanceDefaultBackupPath') DefaultBackupPath |
With traditional physical disks, the data, log and backup paths should be on separate drives. Even with more advanced drive types it can be useful for these to be separated so the drive subsystem isn’t overloaded. Having the defaults set correctly at the server level helps with database configuration.
SQL Server Configuration
Most configurable server items are managed via SSMS or sp_configure. Running sp_configure will also show how these items are configured. Pay attention to the items that are not using the default values. You will want to be sure you have enabled “show advanced options” so you don’t miss anything. This command isn’t available in Azure SQL.
1 2 3 4 5 6 7 |
--Advanced options only needs to be set once. It, and reconfigure, can be skipped after it is set. EXEC sp_configure 'show advanced options',1 GO RECONFIGURE GO EXEC sp_configure GO |
Another, possibly better, option for looking at server-wide configuration settings is sys.configurations. This works in Azure as well as on-prem servers and it shows all configuration values without any switches needing to be enabled. It’s a newer option and doesn’t allow changing the values. Old habits die hard, so I’m still more likely to use sp_configure first, but sys.configurations is easier and more flexible in some ways.
1 2 |
SELECT * FROM sys.configurations |
SQL Server Properties
Many valuable server configuration settings are available via the function SERVERPROPERTY. The following will show these properties. These settings will help the overall picture of your audit, especially when compared to other servers in the enterprise. Be sure to any new options that become available as new editions are released.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
DECLARE @Properties TABLE ( PropertyName varchar(255) ) INSERT INTO @Properties ( PropertyName ) VALUES ('BuildClrVersion') ,('Collation') ,('CollationID') ,('ComparisonStyle') ,('ComputerNamePhysicalNetBIOS') ,('Edition') ,('EditionID') ,('EngineEdition') ,('FilestreamConfiguredLevel') ,('FilestreamEffectiveLevel') ,('FilestreamShareName') ,('HadrManagerStatus') ,('InstanceDefaultBackupPath') ,('InstanceDefaultDataPath') ,('InstanceDefaultLogPath') ,('InstanceName') ,('IsAdvancedAnalyticsInstalled') ,('IsBigDataCluster') ,('IsClustered') ,('IsExternalAuthenticationOnly') ,('IsExternalGovernanceEnabled') ,('IsFullTextInstalled') ,('IsHadrEnabled') ,('IsIntegratedSecurityOnly') ,('IsLocalDB') ,('IsPolybaseInstalled') ,('IsServerSuspendedForSnapshotBackup') ,('IsSingleUser') ,('IsTempDbMetadataMemoryOptimized') ,('IsXTPSupported') ,('LCID') ,('LicenseType') ,('MachineName') ,('NumLicenses') ,('PathSeparator') ,('ProcessID') ,('ProductBuild') ,('ProductBuildType') ,('ProductLevel') ,('ProductMajorVersion') ,('ProductMinorVersion') ,('ProductUpdateLevel') ,('ProductUpdateReference') ,('ProductUpdateType') ,('ProductVersion') ,('ResourceLastUpdateDateTime') ,('ResourceVersion') ,('ServerName') ,('SqlCharSet') ,('SqlCharSetName') ,('SqlSortOrder') ,('SqlSortOrderName') ,('SuspendedDatabaseCount') SELECT @@SERVERNAME ServerName ,PropertyName ,SERVERPROPERTY(PropertyName) PropertyValue FROM @Properties |
Logon triggers
The following can be used to find logon triggers. Remember that there can be more than one logon trigger defined.
1 2 3 4 |
SELECT * FROM sys.server_triggers ST INNER JOIN sys.server_trigger_events STE ON ST.object_id = STE.object_id |
Running traces
Traces that are configured and running will be shown with the following.
1 2 3 |
SELECT traceid,property,value FROM ::fn_trace_getinfo(0) WHERE property = 5 |
If you get any rows back, as shown above, you will want to see what events are getting logged and why the trace is running.
Extended events
You can check running extended events on a server using the following.
1 2 |
SELECT * FROM sys.server_event_sessions |
In Azure SQL Database, you can use the following.
1 2 |
SELECT * FROM sys.database_event_sessions |
These will both show you the event sessions running in the given context. As with traces, understand why they are running and the events getting logged.
Login model
The easiest way to look at this is via the properties of the server in SSMS. I checked the SQL used by SSMS, and the value is stored in a registry key, so you can also run a query to obtain the login model.
1 |
exec master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\MSSQLServer\MSSQLServer', N'LoginMode' |
1 is Windows authentication, and 2 is mixed authentication.
Server roles
Server level roles should be very limited.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
SELECT @@SERVERNAME ServerName ,SP.name LoginName ,SP.type LoginType ,SP.type_desc LoginTypeDescription ,SP.is_disabled LoginIsDisabled ,SP.default_database_name LoginDefaultDatabaseName ,SP.default_language_name LoginDefaultLanguageName ,ROL.name RoleName ,ROL.type RoleType ,ROL.type_desc RoleTypeDescription ,ROL.is_disabled RoleIsDisabled ,CONVERT(date,GETDATE()) ReportDate FROM master.sys.server_role_members SRM INNER JOIN master.sys.server_principals SP ON SRM.member_principal_id = SP.principal_id INNER JOIN master.sys.server_principals ROL ON SRM.role_principal_id = ROL.principal_id |
Permissions assigned directly to logins should also be very limited. Pay special attention to high security permissions such as CONTROL. Review the permission chart for specific details.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
SELECT SP.class ,SP.class_desc ,SP.major_id ,SP.minor_id ,GRE.name GranteeName ,GRE.is_disabled GranteeIsDisabled ,GRO.name GrantorName ,SP.type PermissionType ,SP.permission_name ,SP.state ,SP.state_desc FROM sys.server_permissions SP INNER JOIN sys.server_principals GRE ON SP.grantee_principal_id = GRE.principal_id INNER JOIN sys.server_principals GRO ON SP.grantor_principal_id = GRO.principal_id WHERE GRE.is_disabled = 0 ORDER BY GRE.name ,SP.class_desc ,SP.permission_name |
SQL Jobs and Elastic Jobs
The following shows all SQL jobs, with step details in an XML list and the command run by the job.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
SELECT @@SERVERNAME ServerName ,GETDATE() ReportDate ,sj.name JobName ,sj.date_created ,sj.date_modified ,sj.job_id JobId ,sj.enabled JobEnabled ,'SQL Server Agent Job' JobLocation ,sl.name Owner ,sl.name ExecuteAs ,stp.step_id ,stp.step_name ,stp.subsystem ,stp.Command ,(SELECT CONVERT(xml,'<root>' + (SELECT stp1.step_name, Command, database_name, output_file_name FROM msdb.dbo.sysjobsteps stp1 WHERE stp1.job_id = sj.job_id AND STP1.step_id = stp.step_id FOR XML PATH('')) + '</root>')) StepDetails ,stp.database_name ,stp.output_file_name ,sjs.schedule_id ,ss.name ScheduleName ,ss.enabled ScheduleEnabled ,sjs.next_run_date ,sjs.next_run_time ,CASE ss.freq_type WHEN 1 THEN 'Once' WHEN 4 THEN 'Daily' WHEN 8 THEN 'Weekly' WHEN 16 THEN 'Monthly' WHEN 32 THEN 'Monthly, relative to freq_interval' WHEN 64 THEN 'Run when SQLServerAgent service starts' WHEN 128 THEN 'Run when the computer is idle' END freq_type ,CASE ss.freq_type WHEN 4 THEN 'Every ' + CONVERT(varchar(3),freq_interval) + ' days' WHEN 8 THEN 'Weekly' WHEN 16 THEN 'On the ' + CONVERT(varchar(2),freq_interval) + ' of the month' WHEN 32 THEN CASE freq_interval WHEN 1 THEN 'Sunday' WHEN 2 THEN 'Monday' WHEN 3 THEN 'Tuesday' WHEN 4 THEN 'Wednesday' WHEN 5 THEN 'Thursday' WHEN 6 THEN 'Friday' WHEN 7 THEN 'Saturday' WHEN 8 THEN 'Day' WHEN 9 THEN 'Weekday' WHEN 10 THEN 'Weekend day' END END freq_interval FROM msdb.dbo.sysjobs sj LEFT JOIN msdb.dbo.sysjobsteps stp ON sj.job_id = stp.job_id LEFT JOIN msdb.dbo.sysjobschedules sjs ON sj.job_id = sjs.job_id LEFT JOIN msdb.dbo.sysschedules ss ON sjs.schedule_id = ss.schedule_id LEFT JOIN master.dbo.syslogins sl ON sj.owner_sid = sid ORDER BY sj.name ,ss.name GO |
The following was taken from Microsoft and shows the current jobs and steps in an Elastic Job Server. These can also be created and managed using PowerShell or the Azure portal.
1 2 3 4 5 6 7 8 |
SELECT J.* ,JS.* FROM jobs.jobs J INNER JOIN jobs.jobsteps JS ON J.job_id = JS.job_id AND J.job_version = JS.job_version GO |
Replication
Replication status can be verified and monitored in SSMS.
Each database can be checked for replication information with the following. Replication is a large topic and beyond the scope of this audit. Refer to the Microsoft documentation for additional monitoring scripts. For the SQL configuration audit, it is sufficient to know if the server is used in replication.
1 2 3 4 5 |
SELECT DATABASEPROPERTYEX(DB_NAME(),'IsPublished') ,DATABASEPROPERTYEX(DB_NAME(),'IsMergePublished') ,DATABASEPROPERTYEX(DB_NAME(),'IsSubscribed') ,DATABASEPROPERTYEX(DB_NAME(),'ReplicaID') |
Cluster / Always-on
If a server is clustered, it will be shown in the server properties.
1 2 |
SELECT SERVERPROPERTY('IsClustered') IsClustered |
Always-on high availability setup is shown with the following script.
1 2 3 4 5 6 7 8 9 10 11 |
SELECT hags.primary_replica ,hags.primary_recovery_health ,hags.primary_recovery_health_desc ,hags.secondary_recovery_health ,hags.secondary_recovery_health_desc ,hags.synchronization_health ,hags.synchronization_health_desc FROM sys.dm_hadr_availability_group_states hags INNER JOIN sys.availability_groups ag ON ag.group_id = hags.group_id |
Service Accounts
Service accounts for a server installation can be found with the following.
1 2 |
SELECT * FROM sys.dm_server_services |
SQL Server Audits
SQL Server audits are shown with the following query.
1 2 |
SELECT * FROM sys.dm_server_audit_status |
The Azure portal can also be used to show the audits.
Query Store
If the query store is used, the following can be used to examine the queries configured and stored.
1 2 3 4 |
SELECT * FROM sys.query_store_query QSQ INNER JOIN sys.query_store_query_text QST ON QSQ.query_text_id = QST.query_text_id |
Linked Servers
This will show the configured linked servers. Note that it will also show the local server.
1 2 |
SELECT * FROM sys.servers |
The following was taken from SSMS and shows the configured linked servers and the associated login methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
SELECT srv.name LinkedServerName ,srv.data_source ,srv.provider ,ll.remote_name AS [RemoteUser] ,CAST(ll.uses_self_credential AS bit) AS [Impersonate] ,ll.modify_date AS [DateLastModified] FROM sys.servers AS srv INNER JOIN sys.linked_logins ll ON ll.server_id=CAST(srv.server_id AS int) LEFT OUTER JOIN sys.server_principals sp ON ll.local_principal_id = sp.principal_id GO |
Backups
Backup history can be accessed with the following.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
SELECT BS.Database_name ,BS.type ,CASE BS.type WHEN 'D' THEN 'Database' WHEN 'I' THEN 'Differential database' WHEN 'L' THEN 'Log' WHEN 'F' THEN 'File or filegroup' WHEN 'G' THEN 'Differential file' WHEN 'P' THEN 'Partial' WHEN 'Q' THEN 'Differential partial' END BackupType ,BS.user_name ,BS.first_lsn ,BS.last_lsn ,BS.backup_start_date ,BS.backup_finish_date ,BS.server_name ,BS.compressed_backup_size ,BMF.physical_device_name ,ROW_NUMBER() OVER(PARTITION BY BS.database_name, BS.type ORDER BY BS.database_name, BS.type, BS.backup_start_date DESC) SELECT_CRITERIA FROM msdb.dbo.backupset BS INNER JOIN msdb.dbo.backupmediaset BMS ON BS.media_set_id = BMS.media_set_id INNER JOIN msdb.dbo.backupmediafamily BMF ON BS.media_set_id = BMF.media_set_id |
Azure backup history is accessed via a different DMV.
1 2 |
SELECT * FROM sys.dm_database_backups |
Summary
SQL Server configuration audits can be quite extensive. There is always another rock to turn over when doing an audit of this nature, so it’s important to have a plan before you begin. Key items are backups, any configuration item not set to the default, security related items, and high-availability options. Work with your team on any critical items so they can be addresses as early as possible.
References
- https://learn.microsoft.com/en-us/azure/azure-sql/database/elastic-jobs-powershell-create?view=azuresql
- https://learn.microsoft.com/en-us/azure/azure-sql/database/elastic-jobs-tsql-create-manage?view=azuresql
- https://learn.microsoft.com/en-us/sql/relational-databases/replication/monitor/programmatically-monitor-replication?view=sql-server-ver16
- https://learn.microsoft.com/en-us/sql/sql-server/install/security-considerations-for-a-sql-server-installation?view=sql-server-ver16
- https://learn.microsoft.com/en-us/sql/database-engine/configure-windows/configure-windows-service-accounts-and-permissions?view=sql-server-ver16
- https://learn.microsoft.com/en-us/sql/relational-databases/security/permissions-database-engine?view=sql-server-ver16
Load comments