[SQL]使用DDL triger

[SQL]使用DDL triger

練習一下

問題描述

我們發現有人偷改資料庫的表格定義,希望能知道他偷改了甚麼。

解決方式

以DDL trigger的方式監控資料庫表格的變更狀況,並且將在新的資料庫加入下列的條件限制

1. 禁止建立以 sp_ 為前置詞的預存程序
2. 資料表必須要有主索引鍵

執行的SQL

-- 資料來源:德瑞克老師的上課筆記+自行修改
-- DDL Triggers in SQL Server 2005
-- http://www.sql-server-performance.com/2007/ddl-triggers/
-- CREATE TRIGGER (Transact-SQL)
-- http://technet.microsoft.com/zh-tw/library/ms189799.aspx
-- 使用 sp_executesql
-- http://msdn.microsoft.com/zh-tw/library/ms175170(v=sql.105).aspx

-- 針對DDL觸發程序被觸發時的 XML 資料進行記錄
-- 先建立要儲存的表格
USE master
GO

IF EXISTS
(SELECT name
 FROM
	 sys.tables
 WHERE
	 name = 'EvenTrack') DROP TABLE dbo.EvenTrack
GO
	   
CREATE TABLE EvenTrack
(
	[dbname] sysname,
	[LoginName] sysname,
	[TSQLCommand]nvarchar(max),
	[PostTime] datetime,
)

-- 查詢log表格是否建立
SELECT *
FROM
	master.dbo.EvenTrack;
	
-- 針對log table建立trigger防止修改及刪除
IF EXISTS
(SELECT name
 FROM
	 sys.triggers
 WHERE
	 name = 'TR_EvenTrack_Modify') DROP TRIGGER TR_EvenTrack_Modify;
GO
	
CREATE TRIGGER TR_EvenTrack_Modify
on dbo.EvenTrack
after delete,update
as
BEGIN
SELECT N'此表格禁止修改'
ROLLBACK TRANSACTION
END

-- 檢查 Trigger是否建立
SELECT *
FROM
	sys.triggers
WHERE
	name = 'TR_EvenTrack_Modify';


-- step 2 針對要監控的資料庫設定DDL Trigger
-- 抓取所有使用者資料庫
-- DECLARE @SqlString nvarchar(200);
-- SET @SqlString = 'use ? if( DB_NAME() not in (''master'', ''model'', ''msdb'', ''tempdb'')) print''?''';
-- EXEC sp_msforeachdb @SqlString;
USE adstatus
GO

IF  EXISTS
(SELECT *
 FROM
	 sys.triggers
 WHERE
	 parent_class_desc = 'DATABASE'
	 AND name = N'TR_DATABASE_DDL_TRACKING') DROP TRIGGER [TR_DATABASE_DDL_TRACKING] ON DATABASE
GO	
CREATE TRIGGER TR_DATABASE_DDL_TRACKING
	ON DATABASE FOR DDL_DATABASE_LEVEL_EVENTS
 AS
  DECLARE @PostTime datetime = EVENTDATA().value('(/EVENT_INSTANCE/PostTime)[1]','datetime');
  DECLARE @LoginName sysname = EVENTDATA().value('(/EVENT_INSTANCE/LoginName)[1]','sysname');
  DECLARE @TSQLCommand nvarchar(max) = EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','nvarchar(max)');
INSERT master.dbo.EvenTrack
SELECT db_name()
	 , @LoginName
	 , @TSQLCommand
	 , @PostTime;	
GO

-- 檢查trigger是否建立
SELECT *
FROM
	sys.triggers
WHERE
	parent_class_desc = 'DATABASE'
	AND name = N'TR_DATABASE_DDL_TRACKING'

-- 執行 DDL 命令
CREATE TABLE TestTable (TestTableID int);
GO
ALTER TABLE TestTable add  [TestName]nvarchar(10) not null
GO
DROP TABLE TestTable;
GO

-- 檢查DDL是否有寫入table
SELECT *
FROM
	master.dbo.EvenTrack

-- 試著修改或者刪除log資料都會發生錯誤
DELETE
FROM
	master.dbo.EvenTrack
UPDATE master.dbo.EvenTrack
SET
	PostTime = getdate()
GO

-- 如果之後要建立的資料庫都要套用DDL TRIGGER
-- 可以將TRIGGER建在model資料庫
-- 以下共有三個TRIGGER
-- TR_DATABASE_DDL_TRACKING:針對DDL觸發程序被觸發時的 XML 資料進行記錄
-- TR_DDL_ProcNamingConvention:禁止建立以 sp_ 為前置詞的預存程序
-- TR_DDL_CREATE_TABLE_PK:資料表必須要有主索引鍵
USE model
GO

-- TR_DATABASE_DDL_TRACKING:針對DDL觸發程序被觸發時的 XML 資料進行記錄
IF  EXISTS
(SELECT *
 FROM
	 sys.triggers
 WHERE
	 parent_class_desc = 'DATABASE'
	 AND name = N'TR_DATABASE_DDL_TRACKING') DROP TRIGGER [TR_DATABASE_DDL_TRACKING] ON DATABASE
GO	
CREATE TRIGGER TR_DATABASE_DDL_TRACKING
	ON DATABASE FOR DDL_DATABASE_LEVEL_EVENTS
 AS
  DECLARE @PostTime datetime = EVENTDATA().value('(/EVENT_INSTANCE/PostTime)[1]','datetime');
  DECLARE @LoginName sysname = EVENTDATA().value('(/EVENT_INSTANCE/LoginName)[1]','sysname');
  DECLARE @TSQLCommand nvarchar(max) = EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','nvarchar(max)');
INSERT master.dbo.EvenTrack
SELECT db_name()
	 , @LoginName
	 , @TSQLCommand
	 , @PostTime;	
GO

-- TR_DDL_ProcNamingConvention:禁止建立以 sp_ 為前置詞的預存程序
IF  EXISTS
(SELECT *
 FROM
	 sys.triggers
 WHERE
	 parent_class_desc = 'DATABASE'
	 AND name = N'TR_DDL_ProcNamingConvention') DROP TRIGGER [TR_DDL_ProcNamingConvention] ON DATABASE
GO
CREATE TRIGGER TR_DDL_ProcNamingConvention
ON DATABASE
	FOR CREATE_PROCEDURE
AS 
BEGIN
SET NOCOUNT ON;

  DECLARE @EventData xml;
  DECLARE @ObjectName sysname;
SET @EventData = eventdata();
SET @ObjectName = @EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname');
	
  IF @ObjectName LIKE 'sp[_]%'
  BEGIN
    PRINT N'預存程序的前置詞,不得以 sp_ 開頭';
    ROLLBACK TRAN;
  END;
END;
GO

-- TR_DDL_CREATE_TABLE_PK:資料表必須要有主索引鍵
IF  EXISTS
(SELECT *
 FROM
	 sys.triggers
 WHERE
	 parent_class_desc = 'DATABASE'
	 AND name = N'TR_DDL_CREATE_TABLE_PK') DROP TRIGGER [TR_DDL_CREATE_TABLE_PK] ON DATABASE
GO
CREATE TRIGGER TR_DDL_CREATE_TABLE_PK
ON DATABASE
	FOR CREATE_TABLE, ALTER_TABLE 
AS BEGIN
SET NOCOUNT ON;

  DECLARE @EventData xml;
  DECLARE @SchemaName sysname;
  DECLARE @ObjectName sysname;
  DECLARE @FullName nvarchar(max);
SET @EventData = eventdata();
SET @SchemaName = @EventData.value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname');
SET @ObjectName = @EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname') ;
SET @FullName = quotename(@SchemaName) + '.' + quotename(@ObjectName);

  IF OBJECTPROPERTY(OBJECT_ID(@FullName),'TableHasPrimaryKey') <> 1
  BEGIN
    PRINT N'資料表必須要有:主索引鍵';
    ROLLBACK TRAN;
  END;
END;
GO

-- 查詢TRIGGER是否建立
SELECT *
FROM
	sys.triggers
WHERE
	parent_class_desc = 'DATABASE'

-- 建立新的資料庫
USE master
GO
IF  EXISTS
(SELECT name
 FROM
	 sys.databases
 WHERE
	 name = N'TESTDB1')
BEGIN
	ALTER DATABASE [TESTDB] 
		SET  SINGLE_USER WITH ROLLBACK IMMEDIATE
DROP DATABASE [TESTDB]
END
GO
CREATE DATABASE TESTDB
GO
-- 查詢是否有內建的TRIGGER
SELECT *
FROM
	TESTDB.sys.triggers
WHERE
	parent_class_desc = 'DATABASE'

-- 執行 DDL 命令
USE TESTDB
GO

CREATE TABLE TestTable0 (TestTableID int);
GO
CREATE TABLE TestTablePK (TestTableID int primary key);
GO
CREATE PROCEDURE sp_foo
as
return 'foo test!'
GO
DROP TABLE TestTablePK;
GO

-- 檢查DDL是否有寫入table
SELECT *
FROM
	master.dbo.EvenTrack

-- 刪除範例資料庫
USE master
GO
IF  EXISTS
(SELECT name
 FROM
	 sys.databases
 WHERE
	 name = N'TESTDB')
BEGIN
	ALTER DATABASE [TESTDB] 
		SET  SINGLE_USER WITH ROLLBACK IMMEDIATE
DROP DATABASE [TESTDB]
END
GO

結果畫面

針對特定資料庫監控DDL執行狀況

pic1

變更資料庫的動作如果違反DDL的條件限制將會失效

pic3