web-dev-qa-db-ja.com

トリガーを使用せずにSQL Serverでクエリを実行するクライアントのIDを見つけますか?

現在 Change Data Capture(CDC) を使用してデータの変更を追跡しています。変更を加えたクエリを送信するクライアントのホスト名とIPアドレスを追跡したいと考えています。同じユーザー名でログインしている5つの異なるクライアントがある場合、5つのうちどれがクエリを起動したかを追跡するという難題に直面します。私が見つけた他の疑わしい解決策には、次のコマンドでCDCテーブルを変更することが含まれます。

ALTER TABLE cdc.schema_table_CT 
ADD HostName nvarchar(50) NULL DEFAULT(Host_NAME())

ただし、これは、クエリが起動されたサーバーのホスト名を返し、クエリを起動したクライアントのホスト名は返しません。

この問題を回避する方法はありますか?クライアントのホスト名またはIPアドレス(または他の種類の一意のID)をログに記録するのに役立つもの。トリガーを使用したくありません。システムが遅くなるだけでなく、CDCがシステムテーブルを生成するため、トリガーを設定することは明らかに不可能です。

11
Ritesh Bhakre

CDCについてはわかりませんが、ログインにview server state permissionが含まれている場合は、DMVを使用して情報を取得できます。

これは Books Online here にあります。 IP addressが得られる列を追加するようにクエリを変更しました。

SELECT 
    c.session_id, c.net_transport, c.encrypt_option, c.auth_scheme,
    s.Host_name, s.program_name, s.client_interface_name,
    c.local_net_address, c.client_net_address, s.login_name, s.nt_domain, 
    s.nt_user_name, s.original_login_name, c.connect_time, s.login_time 
FROM sys.dm_exec_connections AS c
JOIN sys.dm_exec_sessions AS s
    ON c.session_id = s.session_id
WHERE c.session_id = SPID;  --session ID you want to track
4
Shanky

「トリガーを使用せずに」と言うとき、それはanyトリガーを意味しますか、それともテーブルの行ごとのトリガーを意味しますか?

私はあなたにmayCONTEXT_INFO()関数の賢明な使用であなたが望むものを得ることができるので尋ねます、しかしあなたはあなたの前に_SET CONTEXT_INFO_が正しく呼び出されたことを確認する必要があるでしょう:操作が行われます。

そのための1つの場所は、次のようにserver-levelログオントリガー(つまり、データベース/オブジェクトレベルのトリガーではない)かもしれません。

_USE master
GO
CREATE TRIGGER tr_audit_login
ON ALL SERVER 
WITH EXECUTE AS 'sa'
AFTER LOGON
AS BEGIN
    BEGIN TRY

        DECLARE @eventdata XML = EVENTDATA();

        IF @eventdata IS NOT NULL BEGIN
            DECLARE @spid INT;
            DECLARE @client_Host VARCHAR(64);
            SET @client_Host    = @eventdata.value('(/EVENT_INSTANCE/ClientHost)[1]',   'VARCHAR(64)');
            SET @spid           = @eventdata.value('(/EVENT_INSTANCE/SPID)[1]',         'INT');

            -- pack the required data into the context data binary
            -- (spid is just an example of packing multiple data items in a single field: you would probably use @@SPID at the point of use, instead)
            DECLARE @context_data VARBINARY(128);
            SET @context_data = CONVERT(VARBINARY(4),  @spid)
                              + CONVERT(VARBINARY(64), @client_Host);

            -- persist the spid and Host into session-level memory
            SET CONTEXT_INFO @context_data;             
        END

    END TRY
    BEGIN CATCH
        /* do better error handling here...
         * logon trigger can lock all users out of server, so i am just swallowing everything
         */
        DECLARE @msg NVARCHAR(4000) = ERROR_MESSAGE();
        RAISERROR('%s', 10, 1, @msg) WITH LOG;
    END CATCH
END
_

次に、デフォルトの制約をテーブルに追加して、コンテキストを格納します(挿入速度を上げるため)。

_ALTER TABLE cdc.schema_table_CT 
ADD ContextInfo varbinary(128) NULL DEFAULT(CONTEXT_INFO())
_

それができたら、そのContextInfo列を少しのスライスアンドダイスでクエリできます。

_SELECT *
    ,spid = CONVERT(INT, SUBSTRING(ContextInfo, 1, 4))
    ,client = CONVERT(VARCHAR(64), SUBSTRING(ContextInfo, 5, 64))
FROM cdc.schema_table_CT
_

技術的には、SUBSTRINGCONVERTをデフォルトの制約の一部として実行し、そこにクライアントIPを格納するだけですが、そこにコンテキスト全体を格納する方が速い場合があります(すべてのINSERT)で行われ、必要なときにのみSELECTの値を抽出します。

SUBSTRINGCONVERTのすべての呼び出しを単一行のインラインテーブル値関数にラップする傾向があるかもしれません。必要に応じて_CROSS APPLY_を使用します。これにより、展開ロジックが1か所に保持されます。

_CREATE FUNCTION fn_context (
    @context_info VARBINARY(128)
)
RETURNS TABLE
AS RETURN (
    SELECT
         spid = CONVERT(INT, SUBSTRING(@context_info, 1, 4))
        ,client = CONVERT(VARCHAR(64), SUBSTRING(@context_info, 5, 64))
)
GO

SELECT * 
FROM cdc.schema_table_CT s
CROSS APPLY dbo.fn_context(s.ContextInfo) c
_

_CONTEXT_INFO_は128バイトのVARBINARYにすぎないことに注意してください。 128バイトに収まりきらないデータが必要な場合は、すべてのデータを保持するテーブルを作成し、その「セッション」の行としてログオントリガーのテーブルに挿入し、_CONTEXT_INFO_を代理キーに設定しますそのテーブルの値

また、これはデフォルトの制約にすぎないため、適切な特権を持つユーザーが保管テーブルのコンテキストデータを上書きするのは簡単です。もちろん、同じことが「監査」スタイルのテーブルの他のすべての列にも当てはまります。

デフォルトではなく永続的な計算列である可能性があればいいのですが、CONTEXT_INFO()関数は非決定的であるため、ノーゴーです(いくつかのFUNCTIONVIEWの周りのトリックですが、私はしません)。

また、_SET CONTEXT_INFO_を自分で呼び出して1日を混乱させる(たとえば、偽の値、または特別に細工された格納された注入)ために十分なアクセス権を持つユーザーにとっては些細なことです。例外を表示し、適切に処理します。

ホスト名については、EVENTDATA()ClientHost要素がIPアドレス(または_<local machine>_インジケーター)を提供すると思います。技術的にはCLRを使用してホスト名に逆DNSルックアップを行うことができますが、これらはすべてのINSERTに対して遅すぎる傾向があるため、notをお勧めします。

haveでホスト名を取得する場合、SQLエージェントジョブを使用して、ローカルDHCPサーバーまたはDNSゾーンファイルからの現在のリースを別のテーブルに定期的に追加することができます。バンドプロセス、および将来のクエリで_LEFT JOIN_に変換(または、ポイントインタイムの場合は、スカラーFUNCTIONでラップしてデフォルトの制約に値を提供)。

繰り返しますが、アプリケーションに何らかの種類の公開コンポーネントがある場合、IPアドレスとホスト名は(NATなどにより)信頼できないことに注意してください。一般に公開されていなくても、ほとんどのIP /ホスト名マップには特定の時間ベースのコンポーネントがあり、これを考慮に入れる必要がある場合があります。

最後に、ログイントリガーを実装する前に、サーバーの専用管理接続をオンにすることは価値があります。ログイントリガーが何らかの方法で壊れると、すべてのユーザーがログインできなくなる可能性があります(sysadminアカウントを含む)。

_USE master
GO
-- you may want to do this, so you have a back-out if the login trigger breaks login
EXEC sp_configure 'remote admin connections', 1 
GO
RECONFIGURE
GO
_

ロックアウトされた場合、DACを使用して、ログイントリガーを削除または無効にすることができます。

_C:\> sqlcmd -S localhost -d master -A
1> DISABLE TRIGGER tr_audit_login ON ALL SERVER
2> GO
_
4
jimbobmcgee

接続バグ を見てください:以下は、関連するスニペットです

この動作は仕様です。 CDCは、変更に関する次の情報を公開するように設計されています:更新された列、操作のタイプ、およびトランザクション情報。監査ソリューションとして設計されていません。全体的なETL時間を短縮するための鍵となる増分データロードを通じて、効率的なExtract Transfer and Loadソリューション(ETL)を実現するために作成されました。その主な目標は、「誰がいつ」ではなく「何が変わったか」を明らかにすることです...そのために、SQL監査機能をお勧めします。

現在のところ、CDCを監査ソリューションに変換する計画はありません。

3
Jens W.