Trova l'identità del client che esegue una query in SQL Server senza utilizzare i trigger?


11

Attualmente sto utilizzando Change Data Capture (CDC) per tenere traccia delle modifiche ai dati e desidero monitorare il nome host e l'indirizzo IP del client che invia la query che ha apportato le modifiche. Se ci sono 5 client diversi che hanno effettuato l'accesso con lo stesso nome utente, si affronta l'enigma del tracciamento di quale dei 5 ha attivato la query. Altre soluzioni speciose che ho trovato includono l'alterazione della tabella CDC con il seguente comando:

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

Tuttavia, questo restituisce il nome host del server su cui è stata attivata la query e non il nome host del client che attiva la query.

C'è un modo per aggirare questo problema? Qualcosa che aiuterebbe a registrare il nome host o l'indirizzo IP (o qualche altro tipo di identità univoca) del client. Non voglio usare i trigger, poiché rallenta il sistema, anche CDC genera tabelle di sistema, quindi apparentemente non è possibile avere un trigger.

Risposte:


4

Non sono sicuro di CDC, ma se il login ha view server state permissionè possibile utilizzare DMV per ottenere alcune informazioni.

Questo è riportato nella documentazione online qui . Ho modificato la query per aggiungere colonne che ti darebbero 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

Quando dici "senza usare i trigger", intendi dei trigger o dei trigger riga per riga sui tavoli?

Lo chiedo perché si potrebbe essere in grado di ottenere quello che vuoi con un uso giudizioso della CONTEXT_INFO()funzione, ma si avrebbe bisogno di assicurare che SET CONTEXT_INFOè stato chiamato correttamente prima le operazioni si svolgono.

Un punto da fare potrebbe essere un trigger di accesso a livello di server (ovvero non un trigger a livello di database / oggetto), in questo modo:

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

È quindi possibile aggiungere il vincolo predefinito alla tabella per archiviare il contesto (per la velocità di inserimento):

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

Una volta che hai quello, puoi interrogare quella ContextInfocolonna con un po 'di slice-and-dice:

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

Tecnicamente, potresti farlo SUBSTRINGe CONVERTcose come parte del tuo vincolo predefinito, e semplicemente archiviare lì l'IP client, ma potrebbe essere più veloce archiviare l'intero contesto lì (come è fatto su tutti INSERT), ed estrarre solo i valori in un SELECTquando ne hai bisogno.

Potrei essere incline a racchiudere tutto il mio SUBSTRINGe CONVERTchiama in una funzione in linea con valori di tabella in linea singola, che farei CROSS APPLYquando necessario. Ciò mantiene la logica di decompressione in un unico posto:

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

Si noti che CONTEXT_INFOè solo un 128 byte VARBINARY. Se hai bisogno di più dati di quanti ne puoi inserire in 128 byte, creerei una tabella per contenere tutti quei dati, inserirmi come riga per quella "sessione" nella tabella nel trigger di accesso e impostare CONTEXT_INFOil valore chiave surrogato di quella tabella

Si noti inoltre che, poiché si tratta solo di un vincolo predefinito, è banale per un utente con privilegi adeguati sovrascrivere i dati di contesto nella tabella at-rest. Naturalmente, lo stesso vale per tutte le altre colonne anche nelle tabelle in stile "audit".

Sarebbe bello se potesse essere una colonna calcolata persistente, piuttosto che un valore predefinito, ma la CONTEXT_INFO()funzione non è deterministica, quindi è un no-go (potresti essere in grado di usare alcuni FUNCTIONtrucchi attorno a a VIEW, ma non lo farei ).

È anche banale per quell'utente con accesso sufficiente a chiamare SET CONTEXT_INFOse stesso e rovinare la tua giornata (ad esempio con valori falsi o iniezione memorizzata appositamente), quindi tratta i contenuti con sospetto e cura, codificali prima della visualizzazione e gestisci le eccezioni bene.

Per quanto riguarda il nome host, penso che l' ClientHostelemento EVENTDATA()ti dia l'indirizzo IP (o un <local machine>indicatore). Mentre tecnicamente potresti usare CLR per eseguire ricerche DNS inverse al nome host, questi tendono ad essere troppo lenti per fare per tutti INSERT, quindi consiglierei di non farlo.

Se è necessario disporre di un nome host, è possibile che si desideri utilizzare un processo SQL Agent per popolare periodicamente una tabella separata con i contratti di locazione correnti dal server DHCP locale o dal file della zona DNS, come processo fuori banda, e LEFT JOINin quello in query future (o racchiudere in uno scalare FUNCTIONper fornire un valore a un vincolo predefinito, per il punto temporale).

Ancora una volta, dovresti notare che, se l'applicazione ha qualsiasi tipo di componente pubblico, gli indirizzi IP e i nomi host sono inaffidabili (ad esempio a causa di NAT). Anche se non è rivolto al pubblico, esiste un certo componente basato sul tempo per la maggior parte delle mappe IP / hostname, che potrebbe essere necessario considerare.

Infine, prima di implementare il trigger di accesso, potrebbe essere utile attivare la connessione amministrativa dedicata del server. Se il trigger di accesso si interrompe in qualche modo, può impedire a tutti gli utenti di accedere (inclusi gli account 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

Se vieni bloccato, il DAC può essere utilizzato per eliminare o disabilitare il trigger di accesso:

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

3

Dai un'occhiata al bug di connessione : Di seguito è riportato lo snippet pertinente

Questo comportamento è in base alla progettazione. CDC è progettato per esporre le seguenti informazioni su una modifica: colonne aggiornate, tipo di operazione e informazioni sulla transazione. Non è stato progettato come soluzione di controllo. È stato creato per consentire soluzioni di trasferimento e caricamento dell'estrazione efficienti (ETL) attraverso il caricamento incrementale dei dati, che è la chiave per ridurre il tempo complessivo ETL. Il suo obiettivo principale è esporre "ciò che è cambiato" e non chi, quando ... Per questo consiglio la funzione di controllo SQL.

A partire da ora non è in programma la trasformazione di CDC in una soluzione di audit.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.