Znajdź tożsamość klienta uruchamiającego zapytanie w SQL Server bez użycia wyzwalaczy?

11

Obecnie używam Change Data Capture (CDC) do śledzenia zmian danych i chcę śledzić nazwę hosta i adres IP klienta przesyłającego zapytanie, które wprowadziło zmiany. Jeśli istnieje 5 różnych klientów zalogowanych za pomocą tej samej nazwy użytkownika, jeden stoi przed zagadką śledzenia, który z 5 uruchomił zapytanie. Inne sprytne rozwiązania, które znalazłem, to zmiana tabeli CDC za pomocą następującego polecenia:

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

Zwraca jednak nazwę hosta serwera, na którym uruchomiono zapytanie, a nie nazwę hosta klienta, który uruchamia zapytanie.

Czy można rozwiązać ten problem? Coś, co pomogłoby zalogować nazwę hosta lub adres IP (lub inną unikalną tożsamość) klienta. Nie chcę używać wyzwalaczy, ponieważ spowalnia system, również CDC generuje tabele systemowe, więc posiadanie wyzwalacza najwyraźniej nie jest możliwe.

Ritesh Bhakre
źródło

Odpowiedzi:

4

Nie jestem pewien co do CDC, ale jeśli login ma view server state permission, możesz użyć DMV, aby uzyskać pewne informacje.

Jest to podane w Books Online tutaj . Zmieniłem zapytanie, aby dodać kolumny, które dałyby ci 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
źródło
4

Kiedy mówisz „bez użycia wyzwalaczy”, masz na myśli jakieś wyzwalacze lub tylko wyzwalacze wiersz po wierszu w tabelach?

Pytam dlatego, że może być w stanie uzyskać to, co chcesz z rozsądnego korzystania z tej CONTEXT_INFO()funkcji, ale trzeba by upewnić się, że SET CONTEXT_INFOzostał wezwany prawidłowo przed swoimi operacje odbywają.

Jednym z takich sposobów może być wyzwalacz logowania na poziomie serwera (tj. Nie wyzwalacz na poziomie bazy danych / obiektu), na przykład:

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

Następnie możesz dodać domyślne ograniczenie do tabeli, aby zapisać kontekst (dla szybkości wstawiania):

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

Gdy już to zrobisz, możesz zapytać tę ContextInfokolumnę odrobiną kromki:

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

Technicznie rzecz biorąc, możesz to zrobić SUBSTRINGi CONVERTtakie rzeczy jako część domyślnego ograniczenia i po prostu zapisać tam adres IP klienta, ale może być szybsze przechowywanie tam całego kontekstu (jak ma to miejsce na każdym INSERT) i wyodrębnianie wartości tylko w SELECTkiedy ich potrzebujesz.

Mogę być skłonny owinąć wszystkie moje SUBSTRINGi CONVERTwywołania w jednorzędową wbudowaną funkcję o wartościach tabelarycznych, co zrobię w razie CROSS APPLYpotrzeby. To utrzymuje logikę rozpakowywania w jednym miejscu:

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

Zauważ, że CONTEXT_INFOjest to tylko 128 bajtów VARBINARY. Jeśli potrzebujesz więcej danych, niż zmieścisz w 128 bajtach, utworzę tabelę do przechowywania wszystkich tych danych, wstawię jako wiersz dla tej „sesji” do tabeli w wyzwalaczu logowania i CONTEXT_INFOustawię wartość klucza zastępczego tej tabeli

Należy również zauważyć, że ponieważ jest to jedynie domyślne ograniczenie, dla odpowiednio uprzywilejowanego użytkownika nadpisanie danych kontekstowych w tabeli spoczynkowej jest banalne. Oczywiście to samo dotyczy również wszystkich innych kolumn w tabelach typu „audyt”.

Byłoby miło, gdyby była to utrwalona kolumna obliczeniowa, a nie domyślna, ale CONTEXT_INFO()funkcja jest niedeterministyczna, więc nie da się (można użyć pewnych FUNCTIONsztuczek wokół VIEW, ale nie zrobiłbym tego ).

Jest to również trywialne dla tego użytkownika z wystarczającym dostępem, aby zadzwonić do SET CONTEXT_INFOsiebie i zepsuć Ci dzień (np. Fałszywymi wartościami lub specjalnie spreparowanym przechowywanym zastrzykiem), więc traktuj zawartość z podejrzliwością i ostrożnością, koduj ją przed wyświetleniem i obsługuj wyjątki dobrze.

Jeśli chodzi o nazwę hosta, myślę, że ClientHostelement EVENTDATA()daje ci adres IP (lub <local machine>wskaźnik). Podczas technicznie przydałby CLR zrobić wyszukiwań odwrotnego DNS z powrotem do hosta, te wydają się być zbyt powolny, aby zrobić dla każdego INSERT, więc polecam nie zrobić.

Jeśli musisz mieć nazwę hosta, możesz użyć zadania agenta SQL, aby okresowo wypełniać osobną tabelę bieżącymi dzierżawami z lokalnego serwera DHCP lub pliku strefy DNS, jako proces pozapasmowy i LEFT JOINdo tego w przyszłe zapytania (lub zawiń skalar, FUNCTIONaby podać wartość domyślnego ograniczenia, na czas).

Znowu należy zauważyć, że jeśli aplikacja zawiera komponent publiczny, adresy IP i nazwy hostów są niewiarygodne (np. Z powodu NAT). Nawet jeśli nie jest publicznie dostępny, istnieje pewien zależny od czasu komponent do większości map adresów IP / nazw hostów, który może wymagać uwzględnienia.

Wreszcie, przed wdrożeniem wyzwalacza logowania, warto włączyć dedykowane połączenie administracyjne na serwerze. Jeśli wyzwalacz logowania ulegnie awarii, może uniemożliwić zalogowanie się wszystkim użytkownikom (w tym kontom 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

Jeśli zostaniesz zablokowany, DAC może zostać użyty do upuszczenia lub wyłączenia wyzwalacza logowania:

C:\> sqlcmd -S localhost -d master -A
1> DISABLE TRIGGER tr_audit_login ON ALL SERVER
2> GO
jimbobmcgee
źródło
3

Spójrz na błąd połączenia : poniżej znajduje się odpowiedni fragment z niego

To zachowanie jest zgodne z projektem. CDC ma na celu ujawnienie następujących informacji o zmianie: zaktualizowanych kolumn, rodzaju operacji i informacji o transakcji. Nie został zaprojektowany jako rozwiązanie kontrolne. Został stworzony, aby umożliwić wydajne rozwiązania transferu i ładowania ekstraktów (ETL) poprzez przyrostowe ładowanie danych, co jest kluczowe dla skrócenia całkowitego czasu ETL. Jego głównym celem jest ujawnienie „co się zmieniło”, a nie kto, kiedy… W tym celu polecam funkcję SQL Audit.

Na razie nie ma planu przekształcenia CDC w rozwiązanie do audytu.

Jens W.
źródło