Найти личность клиента, запускающего запрос в SQL Server без использования триггеров?

11

В настоящее время я использую Change Data Capture (CDC) для отслеживания изменений данных и хочу отслеживать имя хоста и IP-адрес клиента, отправляющего запрос, который внес изменения. Если есть 5 разных клиентов, вошедших в систему с одним и тем же именем пользователя, один сталкивается с проблемой отслеживания того, кто из 5 запустил запрос. Другие конкретные решения, которые я нашел, включают изменение таблицы CDC с помощью следующей команды:

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

Однако при этом возвращается имя хоста сервера, на котором был запущен запрос, а не имя хоста клиента, запустившего запрос.

Есть ли способ обойти эту проблему? Что-то, что помогло бы зарегистрировать имя хоста или IP-адрес (или какой-либо другой вид уникальной идентификации) клиента. Я не хочу использовать триггеры, так как это замедляет работу системы, а также CDC генерирует системные таблицы, поэтому использование триггера, по-видимому, невозможно.

Ритеш Бхакре
источник

Ответы:

4

Я не уверен насчет CDC, но если у view server state permissionвас есть логин, вы можете использовать DMV для получения некоторой информации.

Это дано в Книгах Онлайн здесь . Я изменил запрос, чтобы добавить столбцы, которые бы дали вам 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
Shanky
источник
4

Когда вы говорите «без использования триггеров», подразумеваете ли вы какие-либо триггеры или просто построчные триггеры в таблицах?

Я спрашиваю, потому что вы, возможно , сможете получить то, что вы хотите, при разумном использовании этой CONTEXT_INFO()функции, но вам нужно убедиться, что он SET CONTEXT_INFOбыл вызван правильно, прежде чем ваши операции будут выполнены.

Одним из мест, где это можно сделать, может быть триггер входа на уровне сервера (т. Е. Не триггер уровня базы данных / объекта), например:

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

Технически, вы можете сделать это SUBSTRINGи CONVERTпрочее как часть вашего ограничения по умолчанию и просто сохранить там IP-адрес клиента, но может быть быстрее сохранить весь контекст там (как это делается для каждого INSERT) и извлекать значения только в SELECTкогда они вам нужны.

Я мог бы быть склонен обернуть все мои вызовы SUBSTRINGи CONVERTвызовы в одну строковую встроенную табличную функцию, что я и сделал бы, CROSS APPLYкогда это необходимо. Это сохраняет логику распаковки в одном месте:

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()функция не является детерминированной, поэтому она не нужна (вы могли бы использовать некоторые FUNCTIONхитрости вокруг a VIEW, но я бы не стал ).

Это также тривиально для пользователя с достаточным доступом, чтобы вызвать SET CONTEXT_INFOсебя и испортить свой день (например, с поддельными значениями или специально созданным сохраненным введением), поэтому относитесь к содержимому с подозрением и осторожностью, кодируйте его перед отображением и обрабатывайте исключения Что ж.

Что касается имени хоста, я думаю, что ClientHostэлемент EVENTDATA()дает вам IP-адрес (или <local machine>индикатор). Хотя технически вы могли бы использовать CLR для обратного поиска DNS по имени хоста, они, как правило, слишком медленны для каждого INSERT, поэтому я бы рекомендовал этого не делать.

Если вам нужно иметь имя хоста, вы можете использовать задание агента 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

Если вы заблокированы, ЦАП может быть использован для сброса или отключения триггера входа в систему:

C:\> sqlcmd -S localhost -d master -A
1> DISABLE TRIGGER tr_audit_login ON ALL SERVER
2> GO
jimbobmcgee
источник
3

Пожалуйста, посмотрите на ошибку подключения : ниже приведен соответствующий фрагмент

Такое поведение является особенностью. CDC предназначен для предоставления следующей информации об изменении: обновленные столбцы, тип операции и информация о транзакции. Он не был разработан как решение для аудита. Он был создан для обеспечения эффективных решений извлечения и передачи (ETL) за счет дополнительной загрузки данных, что является ключом к сокращению общего времени ETL. Его главная цель - разоблачить «что изменилось», а не кто, когда ... Для этого я рекомендую функцию аудита SQL.

На данный момент не планируется превращать CDC в решение аудита.

Йенс В.
источник