V předchozím díle jsem se pokusil z velkého množství auditních záznamů vybrat takové, které se od ostatních významým způsobem odlišují, a mohou být tedy považované za anomálie. Zda taková anomálie znamená poruchu systému nebo jeho zneužití, to prozatím ponechme stranou. Při vyhledávání anomálií jsem se zaměřil na obsah (vlastnosti) každého záznamu samostatně.
Ony ty záznamy o událostech ale nevznikají náhodně. Už jen samotný výskyt události v kontextu ostatních nám může poskytnout docela zajímavé informace o chování systému a jeho uživatelů.
Opět bych se chtěl zaměřit na hledání anomálií v jinak běžném chování systémů. Protože pokud bych měl při dohledu běžících systémů něco řešit, pak jsou to právě výjimky z obvyklého chování.
A na to bych se chtěl v tomto pokračování podívat detailněji.
import random
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import datetime
import pytz
import sqlite3
sns.set_style('darkgrid')
Stejně jako v předchozím článku budu načítat data ze stejné auditní databáze s omezením na jeden měsíc. To proto, že i tak jich je docela velké množství.
Výsledkem následujícího kroku je načtení a upravení dat do dataframe df.
SOURCE_DB = "/home/raska/tmp/audit.db"
with sqlite3.connect(SOURCE_DB) as con:
df = pd.read_sql_query("select * from audit", con, index_col='uuid', parse_dates=['ts'])
df.fillna(value=np.nan, inplace=True)
df.info()
<class 'pandas.core.frame.DataFrame'> Index: 370176 entries, 1ee1cac9-c84b-4b2d-a8f3-db6b7eed59fd to e31d4699-a65b-4f2b-84ff-bc2194396cf7 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 ts 370176 non-null datetime64[ns] 1 event 370176 non-null object 2 org 370176 non-null object 3 usr1 21213 non-null object 4 usr2 40299 non-null object 5 usr3 1022 non-null object 6 peer 314766 non-null object dtypes: datetime64[ns](1), object(6) memory usage: 22.6+ MB
Tato databáze je poněkud zvláštní v tom, že obsahuje auditní záznamy z většího množství běžících systémů. To je ten sloupec org označující systém, který záznam vytvořil.
On se každý výše zmíněný systém v provozu chová trochu jinak. Jeho uživatelé využívají jiné služby, on pak sám poskytuje různé služby ostatním systémům. Proto bude asi lepší, když se podívám na každý systém odděleně.
Nejdříve si ale dále upravím data.
V tomto článku mne budou zajímat pouze informace o:
Datum a čas události si ořežu na rozlišení hodin, do většího detailu dnes nepůjdu. Pro přehlednější manipulaci s označením organizace použiji číslo kategorie namísto zdlouhavého UUID.
Takto tedy vypadá nový dataframe X, se kterým budu dále pracovat:
X = df[['event']].copy()
X['org'] = df['org'].astype('category').cat.codes
X['ts'] = df.ts.dt.floor('H')
X
event | org | ts | |
---|---|---|---|
uuid | |||
1ee1cac9-c84b-4b2d-a8f3-db6b7eed59fd | event_K | 6 | 2021–11–01 00:00:00 |
45c2d161–7c92–45c2–924d-5be83c7d7d5f | event_L | 29 | 2021–11–01 00:00:00 |
a56f86fd-20c9–4819–8eb7-bc2680e8fbb6 | event_L | 28 | 2021–11–01 00:00:00 |
3937d0f7-bc74–4bfb-9f03-aec98ce2e097 | event_L | 20 | 2021–11–01 00:00:00 |
b03e848f-4d5a-4e21–81a6-ae9a0d9957c5 | event_K | 6 | 2021–11–01 00:00:00 |
… | … | … | … |
7e8d7c92–90cc-41a1-b3d7–19f30e3004b0 | event_L | 20 | 2021–11–30 23:00:00 |
7c086771-b626–4ba8–86e1-a73a245edeae | event_L | 29 | 2021–11–30 23:00:00 |
1d19cd72-b04a-4bfc-b958–670d5e5674cc | event_L | 28 | 2021–11–30 23:00:00 |
1a2079b8-c28c-45cc-985c-01354cb49085 | event_K | 6 | 2021–11–30 23:00:00 |
e31d4699-a65b-4f2b-84ff-bc2194396cf7 | event_L | 20 | 2021–11–30 23:00:00 |
370176 rows × 3 columns
Nejdříve se zkusím podívat na to, jakými událostmí přispívají jednotlivé organizace do auditní databáze, a jak jsou aktivní.
Jako nejpřehlednější mně připadá zobrazení jako heatmap (překládat název do češtiny jako teplotní mapa mně připadá fakt divné). V x-sové souřadnici jsou označení systémů, v y-nové jsou typy událostí, no a barva obdélníčku vyjadřuje četnost událostí:
sns.displot(X, x="org", y="event", height=4, aspect=4, cbar=True)
plt.xticks(list(range(X.org.min(), X.org.max() + 1)))
plt.show()
Je vidět, že zdaleka ne všechny systémy generují všechny typy událostí. A několik z nich je výrazně aktivnějších než ty ostatní.
Zkusím se na ně tedy podívat blíže, ale tentokrát do pohledu zahrnu také časovou osu, kdy událost vznikla (v rozlišení na hodiny):
sns.displot(X, x="ts", hue="event", col="org", col_wrap=2, height=2, aspect=3).set_xticklabels(rotation=30)
plt.show()
Pro další bádání bude plně postačovat, pokud si vyberu nějakou jednu zajímavou organizaci, a zkusím se povrtat v jejich záznamech. Obdobně bych se mohl zabývat i těmi ostatními, ale to by asi byla dost velká nuda pro všechy, kdo vydrželi číst až do tohoto bodu.
Takže jsem si vybral jednu organizaci a vytvořil si nový dataframe D pro další bádání.
Nový dataframe je výrazně přeformátovaný. V indexu jsou již časové značky pro všechny hodiny ve vybraném intervalu. Sloupce pak představují zkratky pro jednotlivé typy událostí. No a hodnoty v tabulce jsou počty událostí daného typu, které vznikly v dané hodině.
Možná složitě napsáno, takže dále již jen ukázka:
ORG = 28
D = X[X.org == ORG][['event', 'ts']]
D = D.set_index('ts')
D = pd.get_dummies(D.event)
D = D.groupby('ts').aggregate('sum').astype(int).copy()
D
event_A | event_C | event_D | event_E | event_F | event_G | event_H | event_L | |
---|---|---|---|---|---|---|---|---|
ts | ||||||||
2021–11–01 00:00:00 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 13 |
2021–11–01 01:00:00 | 0 | 0 | 0 | 0 | 0 | 3 | 0 | 6 |
2021–11–01 02:00:00 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 5 |
2021–11–01 03:00:00 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 7 |
2021–11–01 04:00:00 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 2 |
… | … | … | … | … | … | … | … | … |
2021–11–30 19:00:00 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 54 |
2021–11–30 20:00:00 | 0 | 1 | 0 | 0 | 0 | 3 | 0 | 31 |
2021–11–30 21:00:00 | 0 | 4 | 0 | 6 | 0 | 7 | 0 | 53 |
2021–11–30 22:00:00 | 0 | 1 | 0 | 0 | 0 | 3 | 0 | 63 |
2021–11–30 23:00:00 | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 31 |
718 rows × 8 columns
A nyní se zkusím podívat na časové průběhy pro všechny typy událostí zvláště:
for col in D.columns:
sns.relplot(data=D, x="ts", y=col, kind='line', height=4, aspect=4).set_xticklabels(rotation=30)
plt.show()
Jak jsem napsal v úvodu, budou mne zajímat anomálie v chování systému.
Co mám nyní k dispozici pro posouzení chování systému s označemím 28? Jsou to časové řady hustoty výskytu jednotlivých událostí v rámci sledovaného období.
Zkusím se tedy podívat na časovou řadu jedné události a pokusím se najít nějaké neobvyklé chování z pohledu počtu vytvořených událostí.
Jako vhodná událost se mně zdá event_L, neboť již od pohledu jsou v grafu vidět dvě až tři podívné špičky.
EVENT = "event_L"
Pro další experimentování si připravím nový dataframe A, ve kterém jsou indexem časové značky a hodnotou počet událostí v daném časovém intervalu. U indexu jsem musel zajistit, aby obsahoval všechny časové značky z celého intervalu, proto tak složitá konstrukce dataframe:
A = pd.DataFrame(index=pd.date_range(start=datetime.datetime(2021, 11, 1), end=datetime.datetime(2021, 11, 30, 23), freq='H'))
A = pd.concat([A, D[EVENT]], axis=1)
A.fillna(value=0, inplace=True)
A.index.name = 'ts'
A.columns = ['value']
A
value | |
---|---|
ts | |
2021–11–01 00:00:00 | 13.0 |
2021–11–01 01:00:00 | 6.0 |
2021–11–01 02:00:00 | 5.0 |
2021–11–01 03:00:00 | 7.0 |
2021–11–01 04:00:00 | 2.0 |
… | … |
2021–11–30 19:00:00 | 54.0 |
2021–11–30 20:00:00 | 31.0 |
2021–11–30 21:00:00 | 53.0 |
2021–11–30 22:00:00 | 63.0 |
2021–11–30 23:00:00 | 31.0 |
720 rows × 1 columns
Nejdříve vyzkouším rozklad časové řady na tři komponenty: trendy, sezónní část a zbytek
Použiji pro to funkci seasonal_decompose z knihovny statsmodels, která počítá rozklad pomocí plovoucího průměru:
from statsmodels.tsa.seasonal import seasonal_decompose
decomposed = seasonal_decompose(A, model='additive', extrapolate_trend='freq')
sns.relplot(data=decomposed.trend, kind='line', color='blue', height=4, aspect=4).set_xticklabels(rotation=30)
sns.relplot(data=decomposed.seasonal, kind='line', color='green', height=4, aspect=4).set_xticklabels(rotation=30)
sns.relplot(data=decomposed.resid, kind='line', color='red', height=4, aspect=4).set_xticklabels(rotation=30)
plt.show()