Auditing Database Snapshots: Free Lunch!

The ability to keep track of what's being executed on a SQL Server instance, or to create a read-only copy of a database at a specific point in time can be very valuable if required by the solution you are developing. But a lesser-known fact about SQL Auditing and Database Snapshots is that not only do they work well on their own, but they also play nicely together.

The Setup

Let's say you work for that awesome company AdventureWorks, which embraces open data so much that you can download their corporate database here. That database contains some super-sensitive payroll data, and they'd like to keep track of everyone who has accessed it. To do this, you set up SQL Auditing to capture all SELECT operations on the HumanResources.EmployeePayHistory table and log them to a file.

 1-- create a server audit
 2USE [master]
 3GO
 4
 5CREATE SERVER AUDIT [AWAudit]
 6TO FILE (
 7FILEPATH = N'C:\Program Files\Microsoft SQL Server\MSSQL11.SQL2012\MSSQL\DATA')
 8WITH (QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE);
 9GO
10
11-- * in a production system you'd probably want a more stringent ON_FAILURE
12-- value than "continue", but for demonstration purposes it's fine
13
14ALTER SERVER AUDIT [AWAudit] WITH (state = ON);
15
16-- create a database audit specification
17USE [AdventureWorks2012]
18GO
19
20CREATE DATABASE AUDIT SPECIFICATION [EmployeePayAuditSpec]
21FOR SERVER AUDIT [AWAudit]
22ADD (SELECT ON OBJECT::[HumanResources].[EmployeePayHistory] BY [public])
23GO
24
25ALTER DATABASE AUDIT SPECIFICATION [EmployeePayAuditSpec] WITH (STATE = ON);

So now anytime anyone selects from that table, there will be a record of it:

 1-- select from the table being audited
 2SELECT TOP (100) * FROM HumanResources.EmployeePayHistory;
 3
 4-- query the audit file
 5SELECT
 6DATEADD(hh, DATEDIFF(hh, GETUTCDATE(), CURRENT_TIMESTAMP), event_time) AS EventTime,
 7session_server_principal_name,
 8database_name,
 9statement
10FROM sys.fn_get_audit_file('C:\Program Files\Microsoft SQL Server\MSSQL11.SQL2012\MSSQL\DATA\*',NULL,NULL);

So far, so good. Now let's continue with our story and say that management wants to be able to run reports throughout the workday that show the state of the database at 6:00 am. You find the appropriate solution to be a database snapshot, so you setup a job to create a snapshot daily at 6:00 am, and another one to drop it late in the evening. All the reports can then query the snapshot and get the exact state of the database as it was at 6:00 that morning.

1-- create snapshot
2CREATE DATABASE AdventureWorks2012_Snap ON
3(NAME = AdventureWorks2012_Data,
4FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL11.SQL2012\MSSQL\DATA\AW2012.ss')
5AS SNAPSHOT OF AdventureWorks2012;

But what about that audit? Since a snapshot contains absolutely everything in the database at the moment it was created, it will have all that sensitive pay history data as well. How can you audit that?

The Free Lunch

The answer is that you don't have to worry. A snapshot contains everything in the database at the moment it was created, and that doesn't just apply to data – that goes for settings too, including audits. If someone was to query an audited table in the snapshot, this will be recorded by the audit just like on the base database. You'll be able to tell the query was run on the snapshot by looking at the database name column:

 1-- query snapshot
 2USE [AdventureWorks2012_Snap]
 3GO
 4
 5SELECT TOP (100) * FROM HumanResources.EmployeePayHistory;
 6
 7-- query audit file
 8SELECT
 9DATEADD(hh, DATEDIFF(hh, GETUTCDATE(), CURRENT_TIMESTAMP), event_time) AS EventTime,
10session_server_principal_name,
11database_name,
12statement
13FROM sys.fn_get_audit_file('C:\Program Files\Microsoft SQL Server\MSSQL11.SQL2012\MSSQL\DATA\*',NULL,NULL);

But what if you don't want the snapshot to be audited?

The Free Lunch is at Grandma's House

I can only speak from personal experience, but whenever I go visit my grandmother, I go knowing that I will be eating whether I'm hungry or not. She loves cooking and watching her grandchildren eat, and – let's face it – there's just no way to say no to grandma.

Similarly, there's no way to say no to a snapshot. However the database was configured the moment the snapshot was created will be reflected in the snapshot as well. It's important to remember that database snapshots are read-only, so even if you try to disable the audit on a snapshot, it won't work:

1USE [AdventureWorks2012_Snap]
2GO
3
4ALTER DATABASE AUDIT SPECIFICATION [EmployeePayAuditSpec] WITH (STATE = OFF);

The only way to create a snapshot without the audit in this case would be to stop the audit, create the snapshot, and then restart the audit. This raises other issues, namely that you probably shouldn't be stopping an audit of sensitive data like that.