¿Encuentra la identidad del cliente disparando una consulta en SQL Server sin usar disparadores?

11

Actualmente estoy usando Change Data Capture (CDC) para rastrear los cambios de datos, y deseo rastrear el nombre de host y la dirección IP del cliente que envía la consulta que realizó los cambios. Si hay 5 clientes diferentes conectados a través del mismo nombre de usuario, uno enfrenta el enigma de rastrear cuál de los 5 activó la consulta. Otras soluciones engañosas que encontré incluyen alterar la tabla CDC con el siguiente comando:

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

Sin embargo, esto devuelve el nombre de host del servidor en el que se activó la consulta, y no el nombre de host del cliente que dispara la consulta.

¿Hay alguna forma de evitar este problema? Algo que ayudaría a registrar el nombre de host o la dirección IP (o algún otro tipo de identidad única) del cliente. No quiero usar disparadores, ya que ralentiza el sistema, también CDC genera tablas del sistema, por lo que aparentemente no es posible tener un disparador activado.

Ritesh Bhakre
fuente

Respuestas:

4

No estoy seguro acerca de los CDC, pero si tiene el inicio de sesión view server state permission, puede usar los DMV para obtener información.

Esto se da en Libros en línea aquí . Cambié la consulta para agregar columnas que le darían 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
fuente
4

Cuando dice "sin usar desencadenantes", ¿quiere decir algún desencadenante o solo desencadenantes fila por fila en las tablas?

Pregunto porque puede obtener lo que desea con un uso juicioso de la CONTEXT_INFO()función, pero deberá asegurarse de que SET CONTEXT_INFOse haya llamado correctamente antes de que se realicen sus operaciones.

Un lugar para hacerlo podría ser un desencadenador de inicio de sesión a nivel de servidor (es decir, no un desencadenador a nivel de base de datos / objeto), de esta manera:

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

Luego puede agregar la restricción predeterminada a su tabla, para almacenar el contexto (para la velocidad de inserción):

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

Una vez que tenga eso, puede consultar esa ContextInfocolumna con un poco de cortar y cortar:

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

Técnicamente, podría hacer eso SUBSTRINGy otras CONVERTcosas como parte de su restricción predeterminada, y simplemente almacenar la IP del cliente allí, pero puede ser más rápido almacenar todo el contexto allí (como se hace en todos INSERT), y solo extraer los valores en un SELECTcuando los necesites

Yo podría estar inclinado para envolver todos mis SUBSTRINGy CONVERTllamadas en una función con valores de tabla en línea de una sola fila, lo que lo haría CROSS APPLYcuando sea necesario. Eso mantiene la lógica de desempaque en un solo lugar:

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

Tenga en cuenta que CONTEXT_INFOsolo es un byte de 128 VARBINARY. Si necesita más datos de los que puede caber en 128 bytes, crearía una tabla para contener todos esos datos, inserte como fila para esa 'sesión' en la tabla en el disparador de inicio de sesión y establezca CONTEXT_INFOel valor de clave sustituto de esa tabla

También debe tener en cuenta que, dado que es solo una restricción predeterminada, es trivial que un usuario con privilegios adecuados sobrescriba esos datos de contexto en la tabla en reposo. Por supuesto, lo mismo es cierto para todas las otras columnas en las tablas de estilo 'auditoría' también.

Sería bueno si pudiera ser una columna computada persistente, en lugar de una predeterminada, pero la CONTEXT_INFO()función no es determinista, por lo que es un no-go (es posible que pueda usar algunos FUNCTIONtrucos alrededor de a VIEW, pero no lo haría )

También es trivial para ese usuario con acceso suficiente para llamarse a SET CONTEXT_INFOsí mismo y arruinar su día (por ejemplo, con valores falsos o inyección almacenada especialmente diseñada), así que trate el contenido con sospecha y cuidado, codifíquelo antes de mostrarlo y maneje las excepciones. bien.

En cuanto al nombre de host, creo que el ClientHostelemento de EVENTDATA()le da la dirección IP (o un <local machine>indicador). Si bien técnicamente podría usar CLR para realizar búsquedas de DNS inverso de nuevo al nombre de host, estos tienden a ser demasiado lentos para todos INSERT, por lo que recomendaría no hacerlo.

Si tiene que tener un nombre de host, es posible que desee utilizar un trabajo de Agente SQL para llenar periódicamente una tabla separada con las concesiones actuales de su servidor DHCP local o archivo de zona DNS, como un proceso fuera de banda, y LEFT JOINpara eso en consultas futuras (o ajuste en un escalar FUNCTIONpara proporcionar un valor a una restricción predeterminada, para un punto en el tiempo).

Nuevamente, debe tener en cuenta que, si la aplicación tiene algún tipo de componente público, las direcciones IP y los nombres de host no son confiables (por ejemplo, debido a NAT). Incluso si no está dirigido al público, hay un cierto componente basado en el tiempo para la mayoría de los mapas de IP / nombre de host, que es posible que deba tener en cuenta.

Finalmente, antes de implementar su desencadenador de inicio de sesión, puede valer la pena activar la conexión de administrador dedicada de su servidor. Si el activador de inicio de sesión se rompe de alguna manera, puede evitar que todos los usuarios inicien sesión (incluidas las cuentas de administrador del sistema):

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

Si queda bloqueado, el DAC se puede usar para soltar o deshabilitar el desencadenador de inicio de sesión:

C:\> sqlcmd -S localhost -d master -A
1> DISABLE TRIGGER tr_audit_login ON ALL SERVER
2> GO
jimbobmcgee
fuente
3

Eche un vistazo al error de conexión : a continuación se muestra el fragmento correspondiente

Este comportamiento es por diseño. Los CDC están diseñados para exponer la siguiente información sobre un cambio: columnas actualizadas, tipo de operación e información de transacción. No ha sido diseñado como una solución de auditoría. Se ha creado para permitir soluciones eficientes de transferencia y carga de extractos (ETL) a través de la carga de datos incremental, que es clave para reducir el tiempo total de ETL. Su objetivo principal es exponer "lo que ha cambiado", no quién, cuándo ... Para eso recomiendo la función de Auditoría SQL.

A partir de ahora no hay ningún plan para transformar CDC en una solución de auditoría.

Jens W.
fuente