Wednesday, 16 September 2015

How to Create an Audit Trigger In SQL Server

Overview

In my previous article, we have discussed about Temporal Tables in SQL Server 2016, and understand much more about it. Now in this article we will discuss about triggers and will know how to create an audit trigger in SQL Server.

In many applications’ database, it is a very difficult task to find who did something and at what time was the activity performed on that application. However, in SQL Server it is easy to find someone’s activity at any time by using some special type of SQL objects like triggers.

What Are Triggers in SQL Server?

In SQL Server, a trigger is a special kind of stored procedure or database object that automatically executes and captures events or actions that a user performs on a SQL Server database. SQL Server comprises of two types of triggers:

  1. DML Triggers (Data Manipulation Language)
  2. DDL Triggers (Data definition Language)

DML Triggers: DML triggers applies when a user performs operations like INSERT, UPDATE, or DELETE in a specified table or view.

DDL Triggers: DDL triggers applies when a user performs operations such as CREATE, ALTER, and DROP. This kind of trigger is used for administrative tasks like; auditing database operations.

Now, after a small description of triggers in SQL Server we will focus on our main topic that is how to create an audit trigger in SQL Server.

Why Do We Need To Create An Audit Trigger?

The basic need to create an audit trigger in SQL Server is for security purpose and these things become evidence at the time of tampering:

  • Client software that was used for accessing data
  • Date and time, at which the change was occurring
  • Data value, prior and after change

It means that all the above info works as evidence when a trigger is fired.

Now we will create an SQL Server Database audit trigger that will audit and store all the security related events of SQL Server.

USE [master]
GO
DROP TABLE [dbo].[SecurityLog]
GO
CREATE TABLE [dbo].[SecurityLog](
      [EventType][nvarchar](128) NULL, 
      [EventTime][datetime] NULL,
      [EventLog][xml] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

Now we will create DDL trigger that will capture and store all connections and security-related events:

USE [master]
GO
IF EXISTS (SELECT * FROM sys.server_triggers 
             WHERE name = 'ddl_trig_capture_security_events')
DROP TRIGGER ddl_trig_capture_security_events
ON ALL SERVER;
GO
CREATE TRIGGER ddl_trig_capture_security_events
ON ALL SERVER;
FOR  LOGON, DDL_SERVER_SECURITY_EVENTS,        
       DDL_DATABASE_SECURITY_EVENTS
AS    
      INSERT INTO [master]..[SecurityLog] (EventType, EventTime, EventLog)      
     SELECT EVENTDATA().value('(/EVENT_INSTANCE/EventType)[1]','nvarchar(128)')              
     ,EVENTDATA().value('(/EVENT_INSTANCE/PostTime)[1]','datetime')             
,EVENTDATA()
GO

Once you create an audit trigger, you can test it using the following command:

USE [master]
GO
CREATE LOGIN [TestDDL] WITH PASSWORD=N'TestDDL'
GO
USE [AdventureWorks2012]
GO
CREATE USER [TestDDL] FOR LOGIN [TestDDL]
GO
ALTER ROLE [db_datareader] ADD MEMBER [TestDDL]
GO
GRANT EXECUTE ON [dbo].[uspGetBillOfMaterials] TO [TestDDL]
GO

SQL Server database audit trigger for event notification

USE [msdb]
GO
--Creating queue
CREATE QUEUE [SecurityEventsQueue]
GO
--Creating service for the queue
CREATE SERVICE [//AdventureWorks.com/SecurityEventsService]
AUTHORIZATION [dbo]
ON QUEUE [dbo].[SecurityEventsQueue]
GO

Creating route for the service

CREATE ROUTE SecurityEventsRoute
WITH SERVICE_NAME = '//AdventureWorks.com/SecurityEventsService',
ADDRESS = 'LOCAL';
GO

Creating Event Notification to capture connection and secrity-related events

USE [msdb]
GO
CREATE EVENT NOTIFICATION NotifySecurityEvents
ON SERVER
FOR AUDIT_LOGIN,       
       AUDIT_LOGOUT,       
      AUDIT_LOGIN_FAILED,       
      DDL_SERVER_SECURITY_EVENTS,       
      DDL_DATABASE_SECURITY_EVENTS
TO SERVICE '//AdventureWorks.com/SecurityEventsService' ,    
           '9D584F73-1796-4494-ADC2-04BDD729FBCE';
GO

We will create a service program that will process the event messages that are generated via Event Notification objects:

IF EXISTS (SELECT * FROM [sys].[objects] WHERE [name] = 'sProcessSecurityEvents')
DROP PROCEDURE [dbo].[sProcessSecurityEvents]
GO
CREATE PROC [dbo].[sProcessSecurityEvents]
AS BEGIN
SET NOCOUNT ON
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
BEGIN TRY
DECLARE  @message_body [xml]             
             ,@EventTime [datetime]             
            ,@EventType [varchar](128)             
           ,@message_type_name [nvarchar](256)                     ,@dialog [uniqueidentifier]

Endless loop

WHILE (1 = 1)
BEGIN
BEGIN TRANSACTION ;

Receive the next available message

WAITFOR (RECEIVE TOP(1)                    
              @message_type_name = [message_type_name],                    
                 @message_body = [message_body],                    
                 @dialog = [conversation_handle]
FROM [dbo].[SecurityEventsQueue]), TIMEOUT 2000

Rollback and exit if no messages were found

IF (@@ROWCOUNT = 0)
BEGIN       
        ROLLBACK TRANSACTION;       
        BREAK;
END;

End conversation of end dialog message

IF (@message_type_name = 'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
BEGIN       
       PRINT 'End Dialog received for dialog # ' + CAST(@dialog as [nvarchar](40));       
       END CONVERSATION @dialog;
       END;
ELSE
BEGIN       
       SET @EventTime = CAST(CAST(@message_body.query('/EVENT_INSTANCE/PostTime/text()') AS [nvarchar](max)) AS [datetime])       
       SET @EventType = CAST(@message_body.query('/EVENT_INSTANCE/EventType/text()') AS [nvarchar](128))       
       INSERT INTO [master]..[SecurityLog] ([EventType], [EventTime], [EventLog])       
      VALUES (@EventType, @EventTime, @message_body)
END
COMMIT TRANSACTION
END --End of loop
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER()             
            ,ERROR_SEVERITY()             
            ,ERROR_STATE()             
            ,ERROR_PROCEDURE()             
            ,ERROR_LINE()             
            ,ERROR_MESSAGE()
END CATCH
END
GO

After successfully created service program, execute the following script to activate service broker queue:

ALTER QUEUE [dbo].[SecurityEventsQueue]        
        WITH STATUS = ON       
        ,ACTIVATION (PROCEDURE_NAME = [sProcessSecurityEvents]       
        ,STATUS = ON       
        ,MAX_QUEUE_READERS = 1       
         ,EXECUTE AS OWNER)
GO

Now test and examine the output of the SecurityLog table.

To track the security-related event we can also use SQL Server audit.

USE [master]
GO
CREATE SERVER AUDIT [Audit-SecurityEvents]
TO FILE
(      FILEPATH = N'D:\Demo_SQLAudit'       
      ,MAXSIZE = 200 MB       
      ,MAX_ROLLOVER_FILES = 2147483647       
       ,RESERVE_DISK_SPACE = OFF )
WITH
(      QUEUE_DELAY = 1000       
          ,ON_FAILURE = CONTINUE )
GO

After this now, we will create an audit specification to audit our SQL server and database security-related events like Login Password change, Database user or object permission change, failed login attempts and server principle change:

CREATE SERVER AUDIT SPECIFICATION [ServerAuditSpecification]
FOR SERVER AUDIT [Audit-SecurityEvents]
ADD (LOGIN_CHANGE_PASSWORD_GROUP),
ADD (DATABASE_PRINCIPAL_CHANGE_GROUP),
ADD (DATABASE_OBJECT_PERMISSION_CHANGE_GROUP),
ADD (FAILED_LOGIN_GROUP),
ADD (SERVER_PRINCIPAL_CHANGE_GROUP)
GO

Conclusion

In this section, we learnt about how SQL Server uses DML and DDL triggers for security purposes. These DML and DDL triggers capture user events that they performed on SQL Server. Although some SQL features such as SQL Audit are not yet fully developed, auditing of SQL server database using DDL triggers have several disadvantages. For example, a user can easily disable the triggers if he has sufficient permissions. Another disadvantage of using trigger in SQL Server is that triggers perform a schema change and then delete the captured information from auditing repository. In addition, the procedure to create an audit trigger in SQL Server is too long and time consuming. Therefore, it is better to use SQL log viewer tool, which saves a significant amount of valuable time and provides you a convenient result.

Wednesday, 2 September 2015

Know About Temporal Tables in SQL Server 2016

Temporal tables were included in SQL Server 2016 as a new feature. These temporal tables are also known as system-versioned tables allow SQL Server for keeping the history of data in table automatically. A temporal table can be defined as a table for which PERIOD definition exists comprising of system columns. These columns are available with data-type of datetme2 where the period of validity is recorded by system. It also has a history table associated with it where all the system records of previous versions are recorded. So the most significant function of Temporal Table is that it allows storing data in table at any point in time. Usually what happens when you execute any delete statement on table, the old data will be overwritten and you will view latest data (after deletion) but with temporal table you can view latest data and also the old data (without deletion). This is because temporal table value of all the entries or records is maintained and at any point can be determined.

Significance of Temporal Tables

ANSI SQL 2011 standard initially introduced the Temporal Tables, which later was released in SQL Server 2016. As mentioned above, this temporal tables or system-versioned tables let users to query updated or deleted data. On the other hand, a normal table would only return the current data from the table. This practical explanation may help: Suppose a column value is updated from 10 to 20, a normal table will retrieve the current value i.e. 20 only. However, temporal table will allow retrieving old value 10 also. This is done by keeping a history table with all the records, which were entered or updated.

Use Cases for Temporal Table:

  • Indulgence of changing business trends
  • Recording data changes done timely
  • Reviewing changes done to the table
  • Recover accidental data changes
  • Recovering application errors
  • Slowly changing dimensions
  • Repairing record-level corruption

Temporal tables or system-versioned tables is actually an assertion table, which means that it records the complete details and entries like update or deletion made to the databases based on physical dates. Versioning is not supported by temporal tables, which means the versioning of records because of logical dates. This conduces that the time must be taken into consideration if you want to maintain the logical change like time.

How To Create A Temporal (System-versioned) Table?

Pre-requisites & Limitations:

  • A Primary Key has to be defined.
  • Two columns for recording start and end date should be defined with data type of datetime2. These columns are referred as SYSTEM_TIME period columns.
  • AFTER triggers are allowed but INSTEAD OF triggers are not allowed.
  • In-memory OLTP cannot be used.
  • Temporal and history table cannot be FILETABLE.
  • Statements like INSERT and UPDATE cannot reference the SYSTEM_TIME period columns.
  • Data available in history table cannot be changed.

Below mentioned script can be used to create a simple system-versioned table:

When you run this command as query, it will create two tables. One parent Temporal/System-versioned Table, and History table. Here the table with the name dbo.TestTemporal will be created. History table is nested under the parent table and if the name of history table is not specified, SQL Server automatically generates it as dbo.MSSQL_TemporalHistoryFor_objectidoftable.

Closer look at result:

History table possesses an identical set of columns but the constraints are removed. It carries its own set of indexes and statistics. In order to improve the performance, your own indexes can be created.

Conclusion

Temporal tables are significant additional feature of SQL Server 2016. As it will track the history of the records entered in the temporal tables. As the history records are maintained well, it is easy to recover the data at any point instead of only current data recovery. Possible use cases for temporal table can be type 2 dimensions in data warehouses, protection for accidental deletes or updates, auditing, etc.