タイトルはかなり自明ですが、なぜ私がこれを必要としているのかについて知りたい場合は、これが必要なのは、アクティブなテーブルの過去の値を保存するアーカイブ/ログテーブルがあり、このためにデータが危険にさらされるリスクを避けたいからです。テーブルに挿入する必要があるのは、アクティブテーブルに変更を記録するために作成したトリガーだけです。まれに、ログテーブルを手動で編集する必要がある場合があります(存在する場合)、「挿入ロック」をオフにします。
SQL Management StudioでSQL Server 2012 Enterpriseを使用しています
これは、証明書とモジュール署名を使用して実現できます(つまり、 ADD SIGNATURE )。 EXECUTE AS
を介したなりすましの使用は煩雑になる可能性があり、他の誰かが「許可された」ユーザーになりすますか、EXECUTE AS
を使用しているモジュールのコンテンツを変更する可能性があります。ただし、モジュール署名:証明書ベースのユーザーを偽装することはできず(最終テストケースを参照)、証明書のパスワードを知らないと、別のモジュールに署名できません。また、署名したモジュール(トリガーなど)を誰かが変更した場合、署名は自動的に削除され、その変更を警告してから、現在の変更で署名を再署名するか、変更を拒否するかを決定できます;-)。
また、トリガーでApplicationName/ProgramNameをトラップすることは、ConnectionStringでその値を渡すのが簡単であるため、信頼できません。
所有権の連鎖を防ぐため、Auditテーブルはメインテーブルとは別のスキーマAuditing
-dbo
-にあることに注意してください。ほとんどのストアドプロシージャもdbo
スキーマ内。
セットアップ
USE [...];
GO
CREATE CERTIFICATE [AuditCert]
ENCRYPTION BY PASSWORD = 'Password Goes Here.'
WITH SUBJECT = 'Restrict Insert Test';
GO
CREATE USER [AuditUser]
FROM CERTIFICATE [AuditCert];
-- no DEFAULT_SCHEMA for Certificate-based Users
GO
CREATE SCHEMA [Auditing]
AUTHORIZATION [AuditUser];
GO
-- DROP TABLE [Auditing].[AuditLog];
CREATE TABLE [Auditing].[AuditLog]
(
AuditLogID INT IDENTITY(1, 1) NOT NULL,
AuditDate DATETIME2 NOT NULL
CONSTRAINT [DF_AuditLog_AuditDate] DEFAULT (SYSDATETIME()),
ImportantStuffID INT,
Column2 VARCHAR(50),
CONSTRAINT [PK_AuditLog] PRIMARY KEY CLUSTERED (AuditLogID ASC)
);
GO
CREATE TABLE [dbo].[ImportantStuff]
(
ImportantStuffID INT IDENTITY(1, 1) NOT NULL,
Column2 VARCHAR(50),
CONSTRAINT [PK_ImportantStuff] PRIMARY KEY CLUSTERED (ImportantStuffID ASC)
);
GO
CREATE TRIGGER [dbo].[AuditImportantStuff]
ON [dbo].[ImportantStuff]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [Auditing].[AuditLog] ([ImportantStuffID], [Column2])
SELECT ins.[ImportantStuffID], ins.[Column2]
FROM inserted ins;
END;
GO
ADD SIGNATURE TO [dbo].[AuditImportantStuff]
BY CERTIFICATE [AuditCert]
WITH PASSWORD = 'Password Goes Here.';
GO
CREATE PROCEDURE [dbo].[AttemptDirectInsert]
(
@ImportantStuffID INT,
@Column2 VARCHAR(50)
)
AS
SET NOCOUNT ON;
INSERT INTO [Auditing].[AuditLog] ([ImportantStuffID], [Column2])
VALUES (@ImportantStuffID, @Column2);
GO
CREATE PROCEDURE [dbo].[ImportantStuff_AddData]
(
@ValueForColumn2 VARCHAR(50)
)
AS
SET NOCOUNT ON;
INSERT INTO [dbo].[ImportantStuff] ([Column2])
VALUES (@ValueForColumn2);
GO
CREATE USER [TestUser]
WITHOUT LOGIN
WITH DEFAULT_SCHEMA = [dbo];
GO
GRANT EXECUTE ON [dbo].[AttemptDirectInsert] TO [TestUser];
GRANT EXECUTE ON [dbo].[ImportantStuff_AddData] TO [TestUser];
GO
テスト
SELECT SESSION_USER, ORIGINAL_LOGIN();
INSERT INTO [Auditing].[AuditLog] ([ImportantStuffID], [Column2]) VALUES (-1, 'test 1');
EXECUTE AS USER = 'TestUser';
SELECT SESSION_USER, ORIGINAL_LOGIN();
INSERT INTO [Auditing].[AuditLog] ([ImportantStuffID], [Column2]) VALUES (-2, 'test 2');
-- Msg 229, Level 14, State 5, Line 102
-- The INSERT permission was denied on the object 'AuditLog', database '...',
-- schema 'Auditing'.
EXEC [dbo].[AttemptDirectInsert]
@ImportantStuffID = -3,
@Column2 = 'test 3';
-- Msg 229, Level 14, State 5, Procedure AttemptDirectInsert, Line 115
-- The INSERT permission was denied on the object 'AuditLog', database '...',
-- schema 'Auditing'.
INSERT INTO [dbo].[ImportantStuff] ([Column2]) VALUES ('test 4');
-- Msg 229, Level 14, State 5, Line 114
-- The INSERT permission was denied on the object 'ImportantStuff', database '...',
-- schema 'dbo'.
EXEC [dbo].[ImportantStuff_AddData]
@ValueForColumn2 = 'test 5';
-- woo hoo!
SELECT * FROM [Auditing].[AuditLog];
-- Msg 229, Level 14, State 5, Line 122
-- The SELECT permission was denied on the object 'AuditLog', database '...',
-- schema 'Auditing'.
REVERT;
SELECT SESSION_USER, ORIGINAL_LOGIN();
SELECT * FROM [Auditing].[AuditLog];
EXECUTE AS USER = 'AuditUser';
-- Msg 15517, Level 16, State 1, Line 143
-- Cannot execute as the database principal because the principal "AuditUser" does not
-- exist, this type of principal cannot be impersonated, or you do not have permission.
[〜#〜]更新[〜#〜]
その他の注意事項:
db_owner
固定データベースロールの誰もがトリガーを無効にできるはずであり、db_datawriter
固定データベースの誰かが使用できる可能性のある回避策が少なくとも1つあるため、ほとんどの場合、偶発的な挿入が防止されます。彼らがかなり狡猾な場合の役割。TRUSTWORTHY
を有効にしたくない場合に非常に役立ちます。EXECUTE AS
を使用すると、現在のセキュリティコンテキストが変更されます。それは本質的に言っています:私はログイン/ユーザーAですが、当面はログイン/ユーザーBINSTEAD OFの権限を使用してください。** 証明書によって署名されたコード以外からの更新を許可しない、監査テーブルのトリガーのほぼ完全なサンプルコード(約75%完了)がありますが、それを完了するには時間が足りません。概念は、プロセス中に証明書がロックされ、ロックエントリに証明書IDが含まれることです。証明書IDが目的の証明書であること、およびトランザクションで証明書が使用されていないか、証明書が使用されていない場合はROLLBACK
であることを確認できます。問題は、VIEW SERVER STATE
を使用するにはsys.dm_tran_locks
が必要であることでした。ただし、証明書ベースのログインを介して付与することができるため、解決するのはかなり簡単な問題であり、同じ証明書でもかまいません。その場合、そこからログインを作成するために、証明書をバックアップしてmaster
に復元できます。次に、ログインにVIEW SERVER STATE
権限を付与し、最後に同じ証明書を使用して監査テーブルのトリガーに署名します(ベーステーブルのトリガーの署名に使用されていたため、そのDBで既に使用されています)。
EXECUTE AS
(なりすましの可能性があるユーザーを信頼する)の使用に満足している場合、代わりの方法は次のとおりです。
CREATE TABLE dbo.Test
(
TestID integer IDENTITY PRIMARY KEY,
SomeDate datetime NOT NULL
);
GO
CREATE TABLE dbo.TestArchive
(
TestID integer PRIMARY KEY,
SomeDate datetime NOT NULL
);
-- Ordinary user with the ability to insert to the Test table
CREATE USER NormalUser WITHOUT LOGIN;
GRANT INSERT ON dbo.Test TO NormalUser;
GRANT SHOWPLAN TO NormalUser; -- Not required, for testing only
GO
-- User used by the trigger to move rows to the Archive table
CREATE USER ArchiveUser WITHOUT LOGIN;
GRANT SHOWPLAN TO ArchiveUser; -- Required if normal users have this permission
GO
-- Give ownership of the Archive table to the Archive user
-- to prevent ownership chaining skipping permission checks
ALTER AUTHORIZATION
ON OBJECT::dbo.TestArchive
TO ArchiveUser;
これはEXECUTE AS
を使用してArchiveUserとしてアーカイブを実行します
CREATE TRIGGER dbo_Test_AI
ON dbo.Test
WITH EXECUTE AS 'ArchiveUser'
AFTER INSERT
AS
BEGIN
-- Insert deleted rows
INSERT dbo.TestArchive
(
TestID,
SomeDate
)
SELECT
D.TestID,
D.SomeDate
FROM
(
-- Remove rows ready to be archived
DELETE dbo.Test
OUTPUT Deleted.TestID, Deleted.SomeDate
WHERE SomeDate <= DATEADD(DAY, -7, GETUTCDATE())
) AS D;
END;
EXECUTE AS USER = 'NormalUser';
GO
-- Able to insert Test rows
INSERT dbo.Test (SomeDate)
VALUES
(DATEADD(DAY, -6, GETUTCDATE())),
(DATEADD(DAY, -5, GETUTCDATE())),
(DATEADD(DAY, -4, GETUTCDATE())),
(DATEADD(DAY, -3, GETUTCDATE())),
(DATEADD(DAY, -2, GETUTCDATE())),
(DATEADD(DAY, -1, GETUTCDATE()));
GO
-- Able to insert a Test row that gets archived
INSERT dbo.Test (SomeDate)
VALUES
(DATEADD(DAY, -7, GETUTCDATE()));
GO
-- Not able to insert to the archive directly
INSERT dbo.TestArchive (TestID, SomeDate)
VALUES (100, GETUTCDATE());
GO
REVERT;
DROP TABLE
dbo.Test,
dbo.TestArchive;
DROP USER ArchiveInsert;
DROP USER NormalUser;
db_datawriterロールのメンバーやデータベース所有者など、非常に特権のあるユーザーがアーカイブテーブルに直接書き込むことを妨げるものではないことに注意してください。証明書ベースの応答も行いません。適切な解決策は、現在のアクセス許可の設定方法、さまざまなユーザーをどの程度信頼しているか、および妄想のレベルによって異なります。