Qualche pensiero su Audit Collection System
[Traduzione del mio post originale in inglese http://www.muscetta.com/2010/03/18/a-few-thoughts-on-sizing-audit-collection-system/ ]
Già da diversi anni, molti clienti collezionavano gli event logs con MOM – perchè non anche i Security log? Alcuni in realtà lo facevano, ma non era abbastanza scalabile; per questo motivo, qualche anno fa Eric Fitzgerald announciò che stava lavorando a Microsoft Audit Collection System. Tuttavia, il tool dal lui scritto non aveva interfaccia grafica… e il resto è storia: esso è stato integrato in System Center Operations Manager. Eppure, ACS resta un componente poco conosciuto di OpsMgr.
Ci sono una serie di risorse sul web che vale la pena ricordare e linkare:
- SecureVantage ACS Resource Kit http://www.securevantage.com/Products/ACSResourceKit.aspx
- Securevantage ha anche dei corsi online chiamati “Master Classes” http://www.securevantage.com/ACSTraining.aspx
- Una Overview (un pò vecchia) http://opsmgr2007.wikidot.com/system:audit-collection-services
- un vecchio post di Kevin Holman http://blogs.technet.com/kevinholman/archive/2008/03/07/acs-internals-part-1.aspx
- ACS Reports per Win2008 http://blogs.technet.com/momteam/archive/2009/05/08/acs-reports-for-windows-2008-and-windows-2008-r2.aspx
- ACS for Cross-Platform http://blogs.msdn.com/scxplat/archive/2009/12/17/cross-platform-audit-collection-services-released.aspx
- Custom ACS Reports di Jimmy Harper http://blogs.technet.com/jimmyharper/archive/2009/12/10/some-custom-acs-reports.aspx
e molti, molti altri – non posso linkarli tutti!
Per quanto mi riguarda, ho iniziato a giocare con ACS fin dai tempi delle prime beta (prima di iniziare a lavorare in Microsoft e ritornare ad usare MOM, nel periodo in cui mi occupavo di sicurezza informatica), ma non ho mai bloggato relativamente a questo pezzo di software.
Dal momento che, di recente, ho svolto parecchio lavoro su ACS (per questioni legate alle normative del Garante della Privacy), ho pensato che valesse la pena raccogliere alcuni pensieri a riguardo in un post – ed eccolo qui.
Anatomia di un calcolo delle dimensioni effettuato “online”
Ciò che vorrei spiegare qui è la strategia e il processo che seguo per analizzare i dati raccolti in un database ACS allo scopo di determinare la migliore strategia di filtraggio: cosa tenere e cosa no – applicando un filtro sul Collector ACS.
La prima cosa con cui parto è di solito uno dei vari fogli Excel che esistono in rete e promettono di aiutare con il sizing del database…. che tuttavia fanno in genere un calcolo del caso peggiore, suggerendo di allocare molto più spazio di quello che poi si dimostra effettivamente necessario. Non riesco a capacitarmi di come alcune persone possano effettivamente calcolare lo spazio richiesto PRIMA dell’implementazione e solo sulla base di formule teoriche (basate su frequenza di eventi al secondo, ruoli dei server, etc), ma io preferisco un approccio molto più “terra terra”: osservo i dati che ACS sta collezionando in assenza di filtri (raccogliendo quindi tutto) per alcuni giorni, e parto da questo dato per rendermi conto di quanto sarebbe la dimensione “a regime” su un periodo più ampio.
Nel caso di una nuova istallazione questo è facile: si accende ACS, si imposta la retention a qualche giorno (una o due settimane massimo), si alloca una dimensione iniziale del db che dovrebbe essere sufficiente a contenere dati per quei giorni, si agganciano tutti i forwarders (agenti)….. e ci si siede ad aspettare di vedere cosa succede.
Quando si torna a vedere 2 settimana dopo avremo un’idea molto chiara del tipo e quantità di eventi che i miei forwarder normalmente tracciano.
Quali e quanti dati stiamo raccogliendo?
Prima di tutto, se non abbiamo cambiato i parametri di grooming dal default, le procedure di grooming creeranno una nuova partizione ogni giorno. Pertanto, il mio primo passo è quello di analizzare quando è grande tale “partizione”. Ma che cos’è esattamente una “partizione” in ACS? E’ un set di 4 tabelle unite da una JOIN:
- dtEvent_GUID
- dtEventData_GUID
- dtPrincipal_GUID
- dtSTrings_GUID
dove GUID rappresenta un nuoco GUID per ogni nuovo giorni, e le 4 tabelle che hanno lo stesso GUIDE, una volta JOINate assieme permettono di accedere a tutti I dati raccolti in una determinata giornata.
Nel database, la tabella dtPartition contiene invece la lista di tutte le partizioni e dei loro GUID, assieme ad informazioni quali la data e ora in cui tali partizioni sono state “aperte” (iniziate) e “chiuse”.
Tanto per avere una stima di massima, possiamo in genere ignorare lo spazio occupato dalle ultime tre tabelle – che sono di solito di modesta dimensione – e solo tenere conto della tabella dtEvent_GUID per ricavare il numero di eventi per quella partizione/giorno: usando la stored procedure “sp_spaceused” su quella tabella otteniamo anche la dimensione di quella partizione nel database. In questo modo finisco per avere una tavola come la seguente:
Partition ID | Status | Partition Start Time | Partition Close Time | Rows | Reserved KB | Total GB |
9b45a567_c848_4a32_9c35_39b402ea0ee2 | 0 | 2/1/2010 2:00 | 2/1/2010 2:00 | 29,749,366 | 7,663,488 | 7,484 |
8d8c8ee1_4c5c_4dea_b6df_82233c52e346 | 2 | 1/31/2010 2:00 | 2/1/2010 2:00 | 28,067,438 | 9,076,904 | 8,864 |
34ce995b_689b_46ae_b9d3_c644cfb66e01 | 2 | 1/30/2010 2:00 | 1/31/2010 2:00 | 30,485,110 | 9,857,896 | 9,627 |
bb7ea5d3_f751_473a_a835_1d1d42683039 | 2 | 1/29/2010 2:00 | 1/30/2010 2:00 | 48,464,952 | 15,670,792 | 15,304 |
ee262692_beae_4d81_8079_470a54567946 | 2 | 1/28/2010 2:00 | 1/29/2010 2:00 | 48,980,178 | 15,836,416 | 15,465 |
7984b5b8_ddea_4e9c_9e51_0ee7a413b4c9 | 2 | 1/27/2010 2:00 | 1/28/2010 2:00 | 51,295,777 | 16,585,408 | 16,197 |
d93b9f0e_2ec3_4f61_b5e0_b600bbe173d2 | 2 | 1/26/2010 2:00 | 1/27/2010 2:00 | 53,385,239 | 17,262,232 | 16,858 |
8ce1b69a_7839_4a05_8785_29fd6bfeda5f | 2 | 1/25/2010 2:00 | 1/26/2010 2:00 | 55,997,546 | 18,105,840 | 17,681 |
19aeb336_252d_4099_9a55_81895bfe5860 | 2 | 1/24/2010 2:00 | 1/24/2010 2:00 | 28,525,304 | 7,345,120 | 7,173 |
1cf70e01_3465_44dc_9d5c_4f3700dc408a | 2 | 1/23/2010 2:00 | 1/23/2010 2:00 | 26,046,092 | 6,673,472 | 6,517 |
f5ec207f_158c_47a8_b15f_8aab177a6305 | 2 | 1/22/2010 2:00 | 1/22/2010 2:00 | 47,818,322 | 12,302,208 | 12,014 |
b48dabe6_a483_4c60_bb4d_93b7d3549b3e | 2 | 1/21/2010 2:00 | 1/21/2010 2:00 | 55,060,150 | 14,155,392 | 13,824 |
efe66c10_0cf2_4327_adbf_bebb97551c93 | 2 | 1/20/2010 2:00 | 1/20/2010 2:00 | 58,322,217 | 15,029,216 | 14,677 |
0231463e_8d50_4a42_a834_baf55e6b4dcd | 2 | 1/19/2010 2:00 | 1/19/2010 2:00 | 61,257,393 | 15,741,248 | 15,372 |
510acc08_dc59_482e_a353_bfae1f85e648 | 2 | 1/18/2010 2:00 | 1/18/2010 2:00 | 64,579,122 | 16,612,512 | 16,223 |
Se ACS è stato istallato da poco e sta girando senza filtri con tutti gli agenti per un paio di settimane, potreste ottenere dei grafici che somigliano a quelli mostrati qui sotto (ricavati dai valori nella tavola qui sopra), che mostrano il trend "settimanale”:
Nell’esempio mostrato si riconosce senza dubbio un pattern settimanale (lunedì-venerdì hanno molti più eventi rispetto ai fine settimana) e possiamo osservare che, sebbene variabili in dimensione, la partizione più grande osservata è di circa 17GB. Arrotondandola a 20GB (tenendo presente che non abbiamo preso in considerazione le tre tabelle aggiuntive ma solo la prima – e principale – della partizione) possiamo predire 20*7 = 140GB a settimana. Dal momento che abbiamo arrotondato per eccesso e che I fine settimana sono più “tranquilli”, questo dovrebbe darci un giosto “cuscinetto” per permettere al sistema di sopportare eventuali “event storms” che dovessero avvenire (in assenza di filtri, la quantità di dati giornaliera è molto più variable – immaginate ad esempio cosa succede con worm che provano a forzare password, e simili “pesti”). E’ oltretutto sempre suggerito avere una certa quantità di spazio libero nel db per permettere alle operazioni di manutenzione e re-indicizzazione di funzionare correttamente.
Ad ogni modo, nell’esempio di cui sopra, questo cliente voleva conservare gli eventi online del database per sei mesi (180giorni) che avrebbero occupato 20*180 = 3600GB = TRE TERABYTE e MEZZO! In questi casi abbiamo veramente bisogno di una strategia di filtraggio per mantenere la dimensione del database sotto controllo e ridurre i costi di gestione (e backup!) dello stesso.
[Nota – se volete automatizzare l’analisi appena spiegata e produrre una tavola e grafici come quelli appena mostrati, visitate l’altro mio post successivo, dove ho condiviso un report pronto per essere usato]
Strategie di Filtering
Una volta osservata la quantità totale degli eventi, bisogna iniziare a guardare di quali eventi si tratta: da quali macchine, per quali utenti, per quali operazione, etc. Questo tipo di analisi va fatta tramite query, o report… ma in genere io preferisco le query perchè posso inventarne variazioni in modo molto veloce.
Non mi sofferemrò sul COME scrivere un filtro in questo articolo: un filtro per il collector è una WMI Notification query, e come scriverlo ed impostarlo è stato già discusso e spiegato molto bene altrove: nella documentazione di SecureVantage sopracitata, ad esempio, e anche nella documentazione ufficiale Microsoft.
Quello su cui mi voglio soffermare qui è, invece, il processo che io seguo e le query che uso per capire da dove viene il “rumore” e quali eventi potrebbero essere filtrati – in modo da ottenere delle stime di quanto spazio disco riusciremmo a risparmiare con l’implementazione di un certo tipo di filtro, o di un altro.
Numero di eventi per utente
--event count by User (with Percentages)
declare @total float
select @total = count(HeaderUser) from AdtServer.dvHeader
select count(HeaderUser),HeaderUser, cast(convert(float,(count(HeaderUser)) / (convert(float,@total)) * 100) as decimal(10,2))
from AdtServer.dvHeader
group by HeaderUser
order by count(HeaderUser) desc
Nel nostro esempio di sopra, nei 14 giorni di osservazione, otteniamo le percentuali seguenti:
Numero di Eventi | HeaderUser Account | Percentuale |
204,904,332 | SYSTEM | 40.79 % |
18,811,139 | LOCAL SERVICE | 3.74 % |
14,883,946 | ANONYMOUS LOGON | 2.96 % |
10,536,317 | appintrauser | 2.09 % |
5,590,434 | mossfarmusr | … |
Ci basta guardare questo dati per capire che, filtrando gli eventi tracciati dai soli account “SYSTEM”, “LOCAL SERVICE” e “ANONYMOUS”, risparmieremmo più del 45% di spazio disco!
Numero di Events per EventID
Allo stesso modo, possiamo osservare come diversi Event IDs abbiano diversi “pesi” sul totale degil eventi tracciati nel database.
--event count by ID (with Percentages)
declare @total float
select @total = count(EventId) from AdtServer.dvHeader
select count(EventId),EventId, cast(convert(float,(count(EventId)) / (convert(float,@total)) * 100) as decimal(10,2))
from AdtServer.dvHeader
group by EventId
order by count(EventId) desc
Otteniamo le seguenti informazioni:
Event ID | Significato/Descrizione | Totale di eventi | Percentuale |
538 | A user logged off | 99,494,648 | 27.63 |
540 | Successful Network Logon | 97,819,640 | 27.16 |
672 | Authentication Ticket Request | 52,281,129 | 14.52 |
680 | Account Used for Logon by (Windows 2000) | 35,141,235 | 9.76 |
576 | Specified privileges were added to a user's access token. | 26,154,761 | 7.26 |
8086 | Custom Application ID | 18,789,599 | 5.21 |
673 | Service Ticket Request | 10,641,090 | 2.95 |
675 | Pre-Authentication Failed | 7,890,823 | 2.19 |
552 | Logon attempt using explicit credentials | 4,143,741 | 1.15 |
539 | Logon Failure - Account locked out | 2,383,809 | 0.66 |
528 | Successful Logon | 1,764,697 | 0.49 |
Io amo le query, ma non dimentichiamo che ACS mette anche a disposizione alcuni report per fare questo tipo di analisi – anche se per quanto sperimentato personalmente, sono di solito più lenti – specialmente su grandi quantità di dati. Inoltre ci sono stati una serie di bug relativamente ai report nelle varie versioni, pertanto preferisco continuare ad usare le mie query. Sotto è mostrato uno di questi report (sebbene lo screenshot è di un ambiente diverso da quello di sopra – nel caso vi steste domandando come mai i numeri non corrispondono).
Questi numeri e percentuali che abbiamo ottenuto dalle prime due query dovrebbero già orientarci in qualche direzione e farci pensare se dobbiamo cambiare/ritoccare la politica di audit direttamente su Windows, o se vogliamo interevenire con un filtro – qui dobbiamo domandarci “se non lo vogliamo collezionare, perchè dovremmo tracciarlo sui sistemi?” – tuttavia non sempre è possibile ottenere la granularità desiderata su Windows, specialmente nelle versioni più vecchie (Windows 2008 ha portato diversi cambiamenti in questo senso).
Inoltre, con una piccola permutazione delle query mostrate prima, possiamo osservare quali utenti stiamo generando la maggior parte del “rumore”, e ottenere il dettaglio di quali EventID, ad esempio la seguente:
--event distribution for a specific user (change the @user) - with percentages for the user and compared with the total #events in the DB
declare @user varchar(255)
set @user = 'SYSTEM'
declare @total float
select @total = count(Id) from AdtServer.dvHeader
declare @totalforuser float
select @totalforuser = count(Id) from AdtServer.dvHeader where HeaderUser = @user
select count(Id), EventID, cast(convert(float,(count(Id)) / convert(float,@totalforuser) * 100) as decimal(10,2)) as PercentageForUser, cast(convert(float,(count(Id)) / (convert(float,@total)) * 100) as decimal(10,2)) as PercentageTotal
from AdtServer.dvHeader
where HeaderUser = @user
group by EventID
order by count(Id) desc
La query appena mostrata è particolarmente importante poichè, ad esempio, potremmo voler filtrare una serie di eventi dall’utente SYSTEM account (per esempio i logon che avvengono quando servizi vengono avviati e fermati sui server) ma potremmo invece voler mantenere altri eventi tracciati da SYSTEM che riteniamo più importanti ai fini di audit, come ad esempio l’evento generato quando il security log viene svuotato da un amministratore:
andando quindi ad osservare la quantità di event ID 517 sul totale degli eventi tracciati da SYSTEM in generale, questi non saranno poi così tanti – possiamo decidere di tenere questi (che sono utili) e scartare gli altri.
Numero di Events per EventID e per User
Possiamo quindi combinare I due approcci: per utente e per EventID
select count(Id),HeaderUser, EventId
from AdtServer.dvHeader
group by HeaderUser, EventId
order by count(Id) desc
Questo produrrà risultati come i seguenti:
che possiamo agilmente copiare e incollare in Excel e produrre una tabella Pivot:
Cluster EventLog Replication
Un ulteriore aspetto che credo non sia molto noto – pertanto ritengo utile mostrare – è il modo in cui i cluster si “comportano” in relazione ad ACS. In realtà non tutti i cluster, ma se la feature di “eventlog replication” è abilitata (che in realtà dovrebbe essere disabilitata, anche per il monitoring….)ciascun nodo del cluster avrà (ed invierà ad ACS collector) eventi non solo per sé stesso ma anche per tutti gli altri nodi.
Non ho ancora trovato un modo per filtrare questi eventi duplicati – a meno di disabilitare del tutto l’eventlog replication – comunque è interessante conoscere questo fenomeno e rendersi conto di quanto sia il peso di questi eventi sul totale. Per fare questo io uso la query seguente che evidenzia gli eventi tracciati da una macchina per conto di un’altra macchina (macchina == network name).
--to spot machines that are cluster nodes with eventlog repliation and write duplicate events (slow)
select Count(Id) as Total,replace(right(AgentMachine, (len(AgentMachine) - patindex('%\%',AgentMachine))),'$','') as ForwarderMachine, EventMachine
from AdtServer.dvHeader
--where ForwarderMachine <> EventMachine
group by EventMachine,replace(right(AgentMachine, (len(AgentMachine) - patindex('%\%',AgentMachine))),'$','')
order by ForwarderMachine,EventMachine
Conclusioni
Quanto mostrato fin’ora è solo una serie di tecniche e “trucchi del mestiere” che uso per analizzare il sizing di un database ACS. Ci sono ovviamente altri modi di osservare questi dati. Di seguito includo tutta una serie di altre query che mi tornano utili in questo tipo di ingaggi presso i clienti. Ce ne potranno essere certamente altre – con piccole modifiche alle query è possibile “osservare” i dati secondo una prospettiva diversa, a seconda delle necessità.
Ho anche pensato che fosse comodo avere tutta una serie di query raccolte/collezionate in una unica pagina, un poco come quella di Kevin Holman per OpsMgr (tra parentesi, alcune di quelle query le abbiamo scritte assieme per l’OpsMgr Health Check!). Enjoy!
Query utilil
--top event ids
select count(EventId), EventId
from AdtServer.dvHeader
group by EventId
order by count(EventId) desc
--event count by ID (with Percentages)
declare @total float
select @total = count(EventId) from AdtServer.dvHeader
select count(EventId),EventId, cast(convert(float,(count(EventId)) / (convert(float,@total)) * 100) as decimal(10,2))
from AdtServer.dvHeader
group by EventId
order by count(EventId) desc
--which machines have ever written event 538
select distinct EventMachine, count(EventId) as total
from AdtServer.dvHeader
where EventID = 538
group by EventMachine
--machines
select * from dtMachine
--machines (more readable)
select replace(right(Description, (len(Description) - patindex('%\%',Description))),'$','')
from dtMachine
--events by machine
select count(EventMachine), EventMachine
from AdtServer.dvHeader
group by EventMachine
--rows where EventMachine field not available (typically events written by ACS itself for chekpointing)
select *
from AdtServer.dvHeader
where EventMachine = 'n/a'
--event count by day
select convert(varchar(20), CreationTime, 102) as Date, count(EventMachine) as total
from AdtServer.dvHeader
group by convert(varchar(20), CreationTime, 102)
order by convert(varchar(20), CreationTime, 102)
--event count by day and by machine
select convert(varchar(20), CreationTime, 102) as Date, EventMachine, count(EventMachine) as total
from AdtServer.dvHeader
group by EventMachine, convert(varchar(20), CreationTime, 102)
order by convert(varchar(20), CreationTime, 102)
--event count by machine and by date (distinuishes between AgentMachine and EventMachine
select convert(varchar(10),CreationTime,102),Count(Id),EventMachine,AgentMachine
from AdtServer.dvHeader
group by convert(varchar(10),CreationTime,102),EventMachine,AgentMachine
order by convert(varchar(10),CreationTime,102) desc ,EventMachine
--event count by User
select count(Id),HeaderUser
from AdtServer.dvHeader
group by HeaderUser
order by count(Id) desc
--event count by User (with Percentages)
declare @total float
select @total = count(HeaderUser) from AdtServer.dvHeader
select count(HeaderUser),HeaderUser, cast(convert(float,(count(HeaderUser)) / (convert(float,@total)) * 100) as decimal(10,2))
from AdtServer.dvHeader
group by HeaderUser
order by count(HeaderUser) desc
--event distribution for a specific user (change the @user) - with percentages for the user and compared with the total #events in the DB
declare @user varchar(255)
set @user = 'SYSTEM'
declare @total float
select @total = count(Id) from AdtServer.dvHeader
declare @totalforuser float
select @totalforuser = count(Id) from AdtServer.dvHeader where HeaderUser = @user
select count(Id), EventID, cast(convert(float,(count(Id)) / convert(float,@totalforuser) * 100) as decimal(10,2)) as PercentageForUser, cast(convert(float,(count(Id)) / (convert(float,@total)) * 100) as decimal(10,2)) as PercentageTotal
from AdtServer.dvHeader
where HeaderUser = @user
group by EventID
order by count(Id) desc
--to spot machines that write duplicate events (such as cluster nodes with eventlog replication enabled)
select Count(Id),EventMachine,AgentMachine
from AdtServer.dvHeader
group by EventMachine,AgentMachine
order by EventMachine
--to spot machines that are cluster nodes with eventlog repliation and write duplicate events (better but slower)
select Count(Id) as Total,replace(right(AgentMachine, (len(AgentMachine) - patindex('%\%',AgentMachine))),'$','') as ForwarderMachine, EventMachine
from AdtServer.dvHeader
--where ForwarderMachine <> EventMachine
group by EventMachine,replace(right(AgentMachine, (len(AgentMachine) - patindex('%\%',AgentMachine))),'$','')
order by ForwarderMachine,EventMachine
--which user and from which machine is target of elevation (network service doing "runas" is a 552 event)
select count(Id),EventMachine, TargetUser
from AdtServer.dvHeader
where HeaderUser = 'NETWORK SERVICE'
and EventID = 552
group by EventMachine, TargetUser
order by count(Id) desc
--by hour, minute and user
--(change the timestamp)... this query is useful to search which users are active in a given time period...
--helpful to spot "peaks" of activities such as password brute force attacks, or other activities limited in time.
select datepart(hour,CreationTime) as Hours, datepart(minute,CreationTime) as Minutes, HeaderUser, count(Id) as total
from AdtServer.dvHeader
where CreationTime < '2010-02-22T16:00:00.000'
and CreationTime > '2010-02-22T15:00:00.000'
group by datepart(hour,CreationTime), datepart(minute,CreationTime),HeaderUser
order by datepart(hour,CreationTime), datepart(minute,CreationTime),HeaderUser