Trouver l'identité du client qui lance une requête dans SQL Server sans utiliser de déclencheurs?


11

J'utilise actuellement Change Data Capture (CDC) pour suivre les modifications de données et je souhaite suivre le nom d'hôte et l'adresse IP du client soumettant la requête qui a effectué les modifications. S'il y a 5 clients différents connectés via le même nom d'utilisateur, l'un est confronté à l'énigme du suivi qui parmi les 5 a déclenché la requête. D'autres solutions spécieuses que j'ai trouvées incluent la modification de la table CDC avec la commande suivante:

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

Toutefois, cela renvoie le nom d'hôte du serveur sur lequel la requête a été déclenchée et non le nom d'hôte du client qui déclenche la requête.

Existe-t-il un moyen de contourner ce problème? Quelque chose qui aiderait à enregistrer le nom d'hôte ou l'adresse IP (ou un autre type d'identité unique) du client. Je ne veux pas utiliser de déclencheurs, car cela ralentit le système, CDC génère également des tables système, donc avoir un déclencheur sur ce n'est apparemment pas possible.

Réponses:


4

Je ne suis pas sûr de CDC, mais si la connexion a, view server state permissionvous pouvez utiliser DMV pour obtenir des informations.

Ceci est donné dans Books Online ici . J'ai changé la requête pour ajouter des colonnes qui vous donneraient 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

Lorsque vous dites «sans utiliser de déclencheurs», voulez-vous dire des déclencheurs ou simplement des déclencheurs ligne par ligne sur les tables?

Je pose la question parce que vous pourrez peut- être obtenir ce que vous voulez avec une utilisation judicieuse de la CONTEXT_INFO()fonction, mais vous devrez vous assurer qu'elle a SET CONTEXT_INFOété appelée correctement avant que vos opérations aient lieu.

Un endroit pour le faire pourrait être un déclencheur d'ouverture de session au niveau du serveur (c'est-à-dire pas un déclencheur au niveau de la base de données / objet), comme ceci:

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

Vous pouvez ensuite ajouter la contrainte par défaut à votre table, pour stocker le contexte (pour la vitesse d'insertion):

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

Une fois que vous avez cela, vous pouvez interroger cette ContextInfocolonne avec un peu de tranches et de dés:

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

Techniquement, vous pouvez faire cela SUBSTRINGet CONVERTfaire des choses dans le cadre de votre contrainte par défaut, et simplement y stocker l'adresse IP du client, mais il peut être plus rapide de stocker le contexte entier là-bas (comme cela se fait sur tous les INSERT), et d'extraire uniquement les valeurs dans un SELECTquand vous en avez besoin.

Je pourrais être enclin à envelopper tous mes appels SUBSTRINGet CONVERTdans une fonction table en ligne à une seule ligne, ce que je ferais CROSS APPLYsi nécessaire. Cela maintient la logique de déballage en un seul endroit:

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

Notez que ce CONTEXT_INFOn'est qu'un 128 octets VARBINARY. Si vous avez besoin de plus de données que vous ne pouvez en contenir en 128 octets, je créerais une table pour contenir toutes ces données, insérer comme ligne pour cette `` session '' dans la table dans le déclencheur d'ouverture de session et définir CONTEXT_INFOla valeur de clé de substitution de cette table

Vous devez également noter que, comme il ne s'agit que d'une contrainte par défaut, il est trivial pour un utilisateur disposant de privilèges appropriés d'écraser ces données de contexte dans la table au repos. Bien sûr, il en va de même pour toutes les autres colonnes des tableaux de style «audit».

Ce serait bien s'il pouvait s'agir d'une colonne calculée persistante, plutôt que d'une valeur par défaut, mais la CONTEXT_INFO()fonction n'est pas déterministe, donc c'est un non-aller (vous pourriez être en mesure d'utiliser une FUNCTIONruse autour d'un VIEW, mais je ne le ferais pas ).

Il est également trivial pour cet utilisateur disposant d'un accès suffisant de s'appeler SET CONTEXT_INFOet de gâcher votre journée (par exemple avec de fausses valeurs ou une injection stockée spécialement conçue), alors traitez le contenu avec suspicion et soin, codez-le avant de l'afficher et gérez les exceptions bien.

Quant au nom d'hôte, je pense que l' ClientHostélément de EVENTDATA()vous donne l'adresse IP (ou un <local machine>indicateur). Bien que vous puissiez techniquement utiliser CLR pour effectuer des recherches DNS inverses sur le nom d'hôte, celles-ci ont tendance à être trop lentes à faire pour tous INSERT, donc je recommanderais de ne pas le faire.

Si vous devez avoir un nom d'hôte, vous souhaiterez peut-être utiliser un travail de l'Agent SQL pour remplir périodiquement une table distincte avec les baux actuels de votre serveur DHCP local ou du fichier de zone DNS, en tant que processus hors bande, et LEFT JOINà cela dans futures requêtes (ou encapsuler dans un scalaire FUNCTIONpour fournir une valeur à une contrainte par défaut, pour un point dans le temps).

Encore une fois, vous devez noter que, si l'application a tout type de composant accessible au public, les adresses IP et les noms d'hôte ne sont pas fiables (par exemple en raison de NAT). Même si elle n'est pas accessible au public, la plupart des mappages IP / noms d'hôte doivent prendre en compte un certain temps.

Enfin, avant d'implémenter votre déclencheur de connexion, il peut être utile d'activer la connexion d'administration dédiée de votre serveur. Si le déclencheur de connexion est interrompu de quelque façon que ce soit, il peut empêcher tous les utilisateurs de se connecter (y compris les comptes 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

Si vous êtes verrouillé, le DAC peut être utilisé pour supprimer ou désactiver le déclencheur de connexion:

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

3

Veuillez jeter un œil au bogue de connexion : voici l'extrait correspondant

Ce comportement est voulu par la conception même du produit. CDC est conçu pour exposer les informations suivantes sur une modification: colonnes mises à jour, type d'opération et informations de transaction. Il n'a pas été conçu comme une solution d'audit. Il a été créé pour permettre des solutions efficaces de transfert et de chargement d'extrait (ETL) grâce à une charge de données incrémentielle qui est essentielle pour réduire le temps ETL global. Son objectif principal est d'exposer "ce qui a changé" et non pas qui, quand ... Pour cela, je recommande la fonction SQL Audit.

Pour l'instant, il n'est pas prévu de transformer CDC en solution d'audit.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.