Pułapki przy edycji pipeline'u Alloy¶
Strona dla osoby zmieniającej defaults/alloy/config.alloy — plik, który
nadaje logom detected_level i rozkłada trafienia WAF-a na pola modsec_*.
Opis wyniku (słownik poziomów, retencja, tabela pól) jest osobno:
Logowanie.
Plik jest force-syncowany
— zmiana w repo trafia na wdrożenia przez git pull && make up, ale też: nikt
nie stroi go u siebie, więc nie ma tu żadnych „wartości użytkownika" do
uszanowania. To kod.
Weryfikacja: make test-alloy¶
Przepuszcza prawdziwy config.alloy przez prawdziwe linie logów
(tests/fixtures/alloy-loglines.txt) przez loki.echo i sprawdza wynikowe
etykiety. Uruchamiaj po każdej zmianie w tym pliku — wszystkie opisane niżej
błędy są dla oka niewidoczne, a ten test je łapie.
Kolejne stage.regex nadpisują ten sam klucz — wygrywa OSTATNI¶
To jest przyczyna, dla której detected_level liczy się bramką, a nie łańcuchem
poprawek.
Plik był ułożony odwrotnie, niż zakładał jego autor
Detektory stały w kolejności od najbardziej zaufanego, co jest naturalną intuicją — i było dokładnie odwrotne do tego, co robi silnik. Ostatni detektor, najmniej wiarygodny, nadpisywał wynik pierwszego.
Dlatego obowiązuje układ:
- każdy detektor pisze do własnego klucza
lvl_*(nigdy do wspólnego), - jedna
stage.templatena końcu wybiera pierwszy niepusty w kolejności zaufania i mapuje go na słownik.
Nie dokładaj normalizacji jako kolejnego stage.replace
Nowa wartość ma dojść przez rozszerzenie bramki. stage.replace
dopisany za nią działa poza kontrolą słownika i przywraca dokładnie ten
problem, który układ lvl_* rozwiązał. Wartość spoza
zamkniętego zbioru siedmiu
nie ma prawa trafić na label — nierozpoznana zostaje unknown.
stage.template zawsze tworzy swój klucz source¶
Odwołanie do klucza, którego stage.json nie utworzył, nie daje pustej
wartości — renderuje dosłowny łańcuch <no value>, który potem jedzie do
Loki jako treść etykiety.
Każdy szablon dotykający pola opcjonalnego musi mieć więc jawną osłonę:
stage.template {
source = "modsec_attack"
template = "{{ if .modsec_attack }}{{ .modsec_attack }}{{ end }}"
}
Dotyczy to całej rodziny modsec_*, bo
wpis audytowy i linia error.log nie mają tego samego zestawu pól.
\berror\b łapie się wewnątrz ścieżki URL¶
Access log zawiera ścieżki takie jak GET /raport/error-summary.html. Granica
słowa \b nie odróżnia tego od komunikatu aplikacji, więc zwykłe żądanie
raportu było raportowane jako błąd aplikacji i zaśmiecało dashboard błędów.
Wzorzec wymaga dziś, żeby przed słowem-poziomem nie stał /. Dokładając
nowy detektor po treści linii, sprawdź go na liniach access logu z fixture'a —
nie tylko na logach Django.
Trafienia WAF-a: dwa wpisy, piętnaście pól, zero labeli strumienia¶
Jedno żądanie zablokowane przez ModSecurity daje dwa wpisy w Loki i oba
niosą pola modsec_*. Rozróżnia je modsec_src (audit / nginx).
Konsekwencje dla agregatów i dla dashboardów są opisane przy WAF-ie:
Dwa wpisy na jedno żądanie.
Dwie rzeczy, o które łatwo się potknąć po stronie Alloya:
- 15 pól
modsec_*idzie do structured metadata, nigdy do labeli strumienia.modsec_uri×modsec_clientjako labele wysadziłyby kardynalność indeksu. - Poziom trafienia to
warn, nieerror— ta sama decyzja co przylimit_req_log_level: powódź zdarzeń bezpieczeństwa nie ma zalewać dashboardu błędów aplikacji. Trafienia mają własny dashboard.
Jedno źródło detected_level¶
Wbudowane discover_log_levels w Loki jest wyłączone — inaczej dwa detektory
pod tą samą nazwą dają w Grafanie sumę dwóch słowników. Szczegóły i powód,
dla którego bliźniaczego discover_service_name wyłączyć się nie da:
Logowanie.
Dokładając cokolwiek, co ustawia detected_level, pamiętaj, że ten plik ma
pozostać jego jedynym producentem.