Bezpieczeństwo i DBMS: o czym należy pamiętać wybierając narzędzia bezpieczeństwa

Bezpieczeństwo i DBMS: o czym należy pamiętać wybierając narzędzia bezpieczeństwa

Nazywam się Denis Rozhkov, jestem szefem rozwoju oprogramowania w firmie Gazinformservice, w zespole produktowym jatoba. Ustawodawstwo i regulacje korporacyjne nakładają pewne wymagania dotyczące bezpieczeństwa przechowywania danych. Nikt nie chce, aby osoby trzecie miały dostęp do poufnych informacji, dlatego w każdym projekcie ważne są następujące kwestie: identyfikacja i uwierzytelnianie, zarządzanie dostępem do danych, zapewnienie integralności informacji w systemie, rejestrowanie zdarzeń związanych z bezpieczeństwem. Dlatego chcę porozmawiać o kilku interesujących kwestiach dotyczących bezpieczeństwa DBMS.

Artykuł powstał na podstawie wystąpienia o godz @DatabasesMeetup, zorganizowany Rozwiązania chmurowe Mail.ru. Jeśli nie chcesz czytać, możesz obejrzeć:


Artykuł będzie składał się z trzech części:

  • Jak zabezpieczyć połączenia.
  • Co to jest audyt działań i jak rejestrować to, co dzieje się po stronie bazy danych i łączyć się z nią.
  • Jak chronić dane w samej bazie danych i jakie technologie są do tego dostępne.

Bezpieczeństwo i DBMS: o czym należy pamiętać wybierając narzędzia bezpieczeństwa
Trzy komponenty bezpieczeństwa DBMS: ochrona połączeń, kontrola aktywności i ochrona danych

Zabezpieczanie połączeń

Z bazą danych można połączyć się bezpośrednio lub pośrednio poprzez aplikacje internetowe. Z reguły użytkownik biznesowy, czyli osoba pracująca z SZBD, wchodzi z nim w interakcję pośrednio.

Zanim zaczniesz mówić o ochronie połączeń, musisz odpowiedzieć na ważne pytania, które określają strukturę środków bezpieczeństwa:

  • Czy jeden użytkownik biznesowy jest odpowiednikiem jednego użytkownika DBMS?
  • czy dostęp do danych DBMS jest zapewniany wyłącznie poprzez kontrolowane przez Ciebie API, czy też dostęp do tabel jest możliwy bezpośrednio;
  • czy SZBD jest przydzielony do oddzielnego chronionego segmentu, kto z nim współdziała i w jaki sposób;
  • czy używane są warstwy Pooling/proxy i pośrednie, które mogą zmieniać informacje o tym, jak połączenie jest zbudowane i kto korzysta z bazy danych.

Zobaczmy teraz, jakich narzędzi można użyć do zabezpieczenia połączeń:

  1. Skorzystaj z rozwiązań klasy zapory baz danych. Dodatkowa warstwa ochrony co najmniej zwiększy przejrzystość tego, co dzieje się w DBMS, a maksymalnie będzie można zapewnić dodatkową ochronę danych.
  2. Stosuj zasady dotyczące haseł. Ich użycie zależy od tego, jak zbudowana jest Twoja architektura. W każdym razie jedno hasło w pliku konfiguracyjnym aplikacji internetowej łączącej się z DBMS nie wystarczy do ochrony. Istnieje wiele narzędzi DBMS, które pozwalają kontrolować, czy użytkownik i hasło wymagają aktualizacji.

    Możesz przeczytać więcej o funkcjach oceniania użytkowników tutaj, możesz także dowiedzieć się o osobach oceniających podatność MS SQL tutaj

  3. Wzbogać kontekst sesji o niezbędne informacje. Jeżeli sesja jest nieprzejrzysta, nie rozumiesz kto w SZBD pracuje w jej ramach, możesz w ramach wykonywanej operacji dodać informację kto co i dlaczego robi. Informacje te można zobaczyć w audycie.
  4. Skonfiguruj SSL, jeśli nie masz separacji sieciowej pomiędzy DBMS a użytkownikami końcowymi; nie jest to oddzielna sieć VLAN. W takich przypadkach konieczne jest zabezpieczenie kanału pomiędzy konsumentem a samym systemem DBMS. Narzędzia zabezpieczające są również dostępne w wersji open source.

Jak wpłynie to na wydajność systemu DBMS?

Spójrzmy na przykład PostgreSQL, aby zobaczyć, jak protokół SSL wpływa na obciążenie procesora, zwiększa taktowanie i zmniejsza TPS oraz czy włączenie go spowoduje zużycie zbyt wielu zasobów.

Ładowanie PostgreSQL za pomocą pgbench to prosty program do uruchamiania testów wydajnościowych. Wykonuje wielokrotnie pojedynczą sekwencję poleceń, prawdopodobnie w równoległych sesjach bazy danych, a następnie oblicza średni współczynnik transakcji.

Test 1 bez SSL i przy użyciu SSL — połączenie jest nawiązywane dla każdej transakcji:

pgbench.exe --connect -c 10 -t 5000 "host=192.168.220.129 dbname=taskdb user=postgres sslmode=require 
sslrootcert=rootCA.crt sslcert=client.crt sslkey=client.key"

vs

pgbench.exe --connect -c 10 -t 5000 "host=192.168.220.129 dbname=taskdb user=postgres"

Test 2 bez SSL i przy użyciu SSL — wszystkie transakcje realizowane są w jednym połączeniu:

pgbench.exe -c 10 -t 5000 "host=192.168.220.129 dbname=taskdb user=postgres sslmode=require
sslrootcert=rootCA.crt sslcert=client.crt sslkey=client.key"

vs

pgbench.exe -c 10 -t 5000 "host=192.168.220.129 dbname=taskdb user=postgres"

Inne ustawienia:

scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
number of transactions per client: 5000
number of transactions actually processed: 50000/50000

Wyniki testu:

 
BEZ SSL
SSL

Dla każdej transakcji nawiązywane jest połączenie

średnie opóźnienie
171.915 ms
187.695 ms

tps łącznie z nawiązywaniem połączeń
58.168112
53.278062

tps z wyłączeniem nawiązywania połączeń
64.084546
58.725846

CPU
24%
28%

Wszystkie transakcje realizowane są w jednym połączeniu

średnie opóźnienie
6.722 ms
6.342 ms

tps łącznie z nawiązywaniem połączeń
1587.657278
1576.792883

tps z wyłączeniem nawiązywania połączeń
1588.380574
1577.694766

CPU
17%
21%

Przy małych obciążeniach wpływ SSL jest porównywalny z błędem pomiaru. Jeżeli ilość przesyłanych danych jest bardzo duża, sytuacja może być inna. Jeśli na transakcję nawiązujemy jedno połączenie (jest to rzadkie zjawisko, zazwyczaj połączenie jest współdzielone pomiędzy użytkownikami), masz dużą liczbę połączeń/rozłączeń, wpływ może być nieco większy. Oznacza to, że może zaistnieć ryzyko zmniejszonej wydajności, jednak różnica nie jest na tyle duża, aby nie stosować ochrony.

Pamiętaj, że jeśli porównasz tryby pracy, istnieje duża różnica: pracujesz w ramach tej samej sesji lub w różnych. Jest to zrozumiałe: zasoby są wydawane na tworzenie każdego połączenia.

Mieliśmy przypadek, gdy podłączaliśmy Zabbixa w trybie zaufania, czyli md5 nie było sprawdzane, nie było potrzeby uwierzytelniania. Następnie klient poprosił o włączenie trybu uwierzytelniania md5. Spowodowało to duże obciążenie procesora i spadek wydajności. Zaczęliśmy szukać sposobów na optymalizację. Jednym z możliwych rozwiązań tego problemu jest wprowadzenie ograniczeń sieciowych, utworzenie oddzielnych sieci VLAN dla systemu DBMS, dodanie ustawień wyjaśniających, kto się skąd łączy i usunięcie uwierzytelniania. Można także zoptymalizować ustawienia uwierzytelniania, aby zmniejszyć koszty podczas włączania uwierzytelniania, ale generalnie stosowanie różnych metod uwierzytelniania wpływa na wydajność i wymaga uwzględnienia tych czynników przy projektowaniu mocy obliczeniowej serwerów (sprzętu) dla SZBD.

Wniosek: w wielu rozwiązaniach nawet drobne niuanse w uwierzytelnianiu mogą znacząco wpłynąć na projekt i jest źle, gdy staje się to jasne dopiero po wdrożeniu w produkcji.

Audyt działania

Audytem może być nie tylko DBMS. Audyt polega na uzyskaniu informacji o tym, co dzieje się w poszczególnych segmentach. Może to być zapora bazy danych lub system operacyjny, na którym zbudowany jest system DBMS.

W komercyjnych systemach DBMS na poziomie przedsiębiorstwa wszystko jest w porządku z audytem, ​​ale w przypadku oprogramowania typu open source - nie zawsze. Oto, co ma PostgreSQL:

  • log domyślny - wbudowane logowanie;
  • rozszerzenia: pgaudit - jeśli domyślne logowanie nie jest dla Ciebie wystarczające, możesz skorzystać z osobnych ustawień, które rozwiążą niektóre problemy.

Dodatek do relacji w filmie:

„Podstawowe rejestrowanie instrukcji może być zapewnione przez standardową funkcję rejestrowania z log_statement = all.

Jest to akceptowalne w przypadku monitorowania i innych zastosowań, ale nie zapewnia poziomu szczegółowości wymaganego zazwyczaj w przypadku audytu.

Nie wystarczy mieć listę wszystkich operacji wykonanych na bazie danych.

Powinna także istnieć możliwość znalezienia konkretnych stwierdzeń, które interesują audytora.

Standardowe logowanie pokazuje, czego żądał użytkownik, podczas gdy pgAudit skupia się na szczegółach tego, co się stało, gdy baza danych wykonała zapytanie.

Na przykład audytor może chcieć sprawdzić, czy konkretna tabela została utworzona w udokumentowanym oknie konserwacji.

Może się to wydawać prostym zadaniem z podstawowym audytem i grepem, ale co by było, gdybyś zobaczył coś takiego (celowo mylącego) przykład:

DO$$
ZACZYNAĆ
WYKONAJ „Utwórz import TABELI” || 'ant_table(id int)';
KONIEC$$;

Standardowe logowanie da ci to:

LOG: instrukcja: DO $$
ZACZYNAĆ
WYKONAJ „Utwórz import TABELI” || 'ant_table(id int)';
KONIEC$$;

Wydaje się, że znalezienie interesującej tabeli może wymagać pewnej znajomości kodu w przypadkach, gdy tabele są tworzone dynamicznie.

Nie jest to idealne rozwiązanie, ponieważ lepiej byłoby po prostu wyszukiwać według nazwy tabeli.

W tym miejscu przydaje się pgAudit.

Dla tych samych danych wejściowych wygeneruje następujące dane wyjściowe w dzienniku:

AUDYT: SESJA,33,1,FUNKCJA,ZROBIĆ,,,ZROBIĆ $$
ZACZYNAĆ
WYKONAJ „Utwórz import TABELI” || 'ant_table(id int)';
KONIEC$$;"
AUDYT: SESJA,33,2,DDL,UTWÓRZ TABELĘ,TABELA,public.important_table,UTWÓRZ TABELĘ ważna_tabela (id INT)

Rejestrowany jest nie tylko blok DO, ale także pełny tekst CREATE TABLE z typem instrukcji, typem obiektu i pełną nazwą, co ułatwia wyszukiwanie.

Podczas rejestrowania instrukcji SELECT i DML pgAudit można skonfigurować tak, aby rejestrował oddzielny wpis dla każdej relacji, do której odwołuje się instrukcja.

Aby znaleźć wszystkie instrukcje, które dotyczą określonej tabeli, nie jest wymagane analizowanie (*). "

Jak wpłynie to na wydajność systemu DBMS?

Przeprowadźmy testy z włączoną pełną inspekcją i zobaczmy, co stanie się z wydajnością PostgreSQL. Włączmy maksymalne rejestrowanie bazy danych dla wszystkich parametrów.

W pliku konfiguracyjnym prawie nic nie zmieniamy, najważniejsze jest włączenie trybu debug5, aby uzyskać maksimum informacji.

postgresql.conf

log_destination = 'stderr'
rejestrowanie_kolekcjonera = włączone
log_truncate_on_rotation = włączone
log_rotation_age = 1d
log_rotation_size = 10 MB
log_min_messages = debugowanie5
log_min_error_statement = debugowanie5
log_min_duration_statement = 0
debug_print_parse = włączone
debug_print_rewriting = włączone
debug_print_plan = włączone
debug_pretty_print = włączone
log_checkpoints = włączone
log_connections = włączone
log_disconnections = włączone
log_duration = włączone
nazwa_hosta_logowania = włączone
log_lock_waits = włączone
log_replication_commands = włączone
log_temp_files = 0
log_timezone = 'Europa/Moskwa'

Na systemie DBMS PostgreSQL o parametrach 1 CPU, 2,8 GHz, 2 GB RAM, 40 GB HDD przeprowadzamy trzy testy obciążeniowe za pomocą poleceń:

$ pgbench -p 3389 -U postgres -i -s 150 benchmark
$ pgbench -p 3389 -U postgres -c 50 -j 2 -P 60 -T 600 benchmark
$ pgbench -p 3389 -U postgres -c 150 -j 2 -P 60 -T 600 benchmark

Wyniki testu:

Brak logowania
Z logowaniem

Całkowity czas wypełniania bazy danych
43,74 sek
53,23 sek

RAM
24%
40%

CPU
72%
91%

Test 1 (50 połączeń)

Liczba transakcji w ciągu 10 minut
74169
32445

Transakcje/sek
123
54

Średnie opóźnienie
405 ms
925 ms

Test 2 (150 połączeń na 100 możliwych)

Liczba transakcji w ciągu 10 minut
81727
31429

Transakcje/sek
136
52

Średnie opóźnienie
550 ms
1432 ms

O rozmiarach

Rozmiar bazy danych
2251 MB
2262 MB

Rozmiar dziennika bazy danych
0 MB
4587 MB

Konkluzja: pełny audyt nie jest zbyt dobry. Dane z audytu będą tak duże, jak dane w samej bazie danych, a nawet większe. Ilość logów generowanych podczas pracy z systemem DBMS jest częstym problemem w środowisku produkcyjnym.

Spójrzmy na inne parametry:

  • Prędkość nie zmienia się zbytnio: bez logowania – 43,74 sekundy, z logowaniem – 53,23 sekundy.
  • Wydajność pamięci RAM i procesora ucierpi, ponieważ konieczne będzie wygenerowanie pliku audytu. Jest to również zauważalne w produktywności.

Naturalnie wraz ze wzrostem liczby połączeń wydajność nieznacznie się pogorszy.

W korporacjach posiadających audyt jest jeszcze trudniej:

  • jest dużo danych;
  • audyt potrzebny jest nie tylko poprzez syslog w SIEM, ale także w plikach: jeśli coś się stanie z syslogiem, w pobliżu bazy danych musi znajdować się plik, w którym zapisywane są dane;
  • do audytu potrzebna jest osobna półka, aby nie marnować dysków I/O, bo zajmuje dużo miejsca;
  • Zdarza się, że pracownicy bezpieczeństwa informacji wszędzie potrzebują standardów GOST, wymagają identyfikacji państwowej.

Ograniczanie dostępu do danych

Przyjrzyjmy się technologiom używanym do ochrony danych i dostępu do nich w komercyjnych systemach DBMS i open source.

Czego ogólnie możesz użyć:

  1. Szyfrowanie i zaciemnianie procedur i funkcji (Zawijanie) - czyli oddzielne narzędzia i narzędzia, które sprawiają, że czytelny kod staje się nieczytelny. To prawda, że ​​​​nie można tego zmienić ani zrefaktoryzować. Takie podejście jest czasami wymagane przynajmniej po stronie DBMS - logika ograniczeń licencji lub logika autoryzacji jest szyfrowana precyzyjnie na poziomie procedury i funkcji.
  2. Ograniczanie widoczności danych według wierszy (RLS) ma miejsce wtedy, gdy różni użytkownicy widzą jedną tabelę, ale inny układ wierszy w niej zawartych, czyli czegoś nie można komuś pokazać na poziomie wiersza.
  3. Edycja wyświetlanych danych (Maskowanie) polega na tym, że użytkownicy w jednej kolumnie tabeli widzą albo dane, albo tylko gwiazdki, czyli dla niektórych użytkowników informacja zostanie zamknięta. Technologia określa, który użytkownik jest wyświetlany, na podstawie jego poziomu dostępu.
  4. Bezpieczeństwo Kontrola dostępu DBA/Aplikacji DBA/DBA polega raczej na ograniczaniu dostępu do samego systemu DBMS, co oznacza, że ​​pracownicy zajmujący się bezpieczeństwem informacji mogą zostać oddzieleni od administratorów baz danych i administratorów aplikacji. Niewiele jest takich technologii w open source, ale jest ich mnóstwo w komercyjnych systemach DBMS. Są potrzebne, gdy do samych serwerów ma wielu użytkowników.
  5. Ograniczanie dostępu do plików na poziomie systemu plików. Możesz nadawać uprawnienia i uprawnienia dostępu do katalogów, tak aby każdy administrator miał dostęp tylko do niezbędnych danych.
  6. Obowiązkowy dostęp i czyszczenie pamięci – technologie te są rzadko stosowane.
  7. Kompleksowe szyfrowanie bezpośrednio z systemu DBMS to szyfrowanie po stronie klienta z zarządzaniem kluczami po stronie serwera.
  8. Szyfrowanie danych. Na przykład szyfrowanie kolumnowe ma miejsce wtedy, gdy używany jest mechanizm szyfrujący pojedynczą kolumnę bazy danych.

Jak to wpływa na wydajność systemu DBMS?

Spójrzmy na przykład szyfrowania kolumnowego w PostgreSQL. Istnieje moduł pgcrypto, który pozwala na przechowywanie wybranych pól w formie zaszyfrowanej. Jest to przydatne, gdy tylko niektóre dane są cenne. Aby odczytać zaszyfrowane pola, klient przesyła klucz deszyfrujący, serwer odszyfrowuje dane i zwraca je klientowi. Bez klucza nikt nie będzie mógł nic zrobić z Twoimi danymi.

Przetestujmy z pgcrypto. Stwórzmy tabelę z zaszyfrowanymi danymi i zwykłymi danymi. Poniżej znajdują się polecenia do tworzenia tabel, już w pierwszej linii znajduje się przydatna komenda - utworzenie samego rozszerzenia z rejestracją w DBMS:

CREATE EXTENSION pgcrypto;
CREATE TABLE t1 (id integer, text1 text, text2 text);
CREATE TABLE t2 (id integer, text1 bytea, text2 bytea);
INSERT INTO t1 (id, text1, text2)
VALUES (generate_series(1,10000000), generate_series(1,10000000)::text, generate_series(1,10000000)::text);
INSERT INTO t2 (id, text1, text2) VALUES (
generate_series(1,10000000),
encrypt(cast(generate_series(1,10000000) AS text)::bytea, 'key'::bytea, 'bf'),
encrypt(cast(generate_series(1,10000000) AS text)::bytea, 'key'::bytea, 'bf'));

Następnie spróbujmy utworzyć próbkę danych z każdej tabeli i przyjrzyjmy się momentom wykonania.

Wybieranie z tabeli bez funkcji szyfrowania:

psql -c "timing" -c "select * from t1 limit 1000;" "host=192.168.220.129 dbname=taskdb
user=postgres sslmode=disable" > 1.txt

Stoper jest włączony.

  identyfikator | tekst1 | tekst2
——+——-+——-
1 | 1 | 1
2 | 2 | 2
3 | 3 | 3
...
997 | 997 | 997
998 | 998 | 998
999 | 999 | 999
1000 | 1000 | 1000
(1000 linii)

Czas: 1,386 ms

Wybór z tabeli z funkcją szyfrowania:

psql -c "timing" -c "select id, decrypt(text1, 'key'::bytea, 'bf'),
decrypt(text2, 'key'::bytea, 'bf') from t2 limit 1000;"
"host=192.168.220.129 dbname=taskdb user=postgres sslmode=disable" > 2.txt

Stoper jest włączony.

  identyfikator | odszyfrować | odszyfrować
——+—————+————
1 | x31 | x31
2 | x32 | x32
3 | x33 | x33
...
999 | x393939 | x393939
1000 | x31303030 | x31303030
(1000 linii)

Czas: 50,203 ms

Wyniki testu:

 
Bez szyfrowania
Pgcrypto (odszyfruj)

Próbka 1000 wierszy
1,386 ms
50,203 ms

CPU
15%
35%

RAM
 
+ 5%

Szyfrowanie ma duży wpływ na wydajność. Można zauważyć, że czas się wydłużył, ponieważ operacje deszyfrowania zaszyfrowanych danych (a deszyfrowanie jest zwykle nadal opakowane w logikę) wymagają znacznych zasobów. Oznacza to, że pomysł szyfrowania wszystkich kolumn zawierających niektóre dane jest obarczony spadkiem wydajności.

Jednak szyfrowanie nie jest złotym środkiem, który rozwiązuje wszystkie problemy. Odszyfrowane dane i klucz deszyfrujący podczas procesu deszyfrowania i przesyłania danych znajdują się na serwerze. Dlatego klucze mogą zostać przechwycone przez osobę mającą pełny dostęp do serwera bazy danych, na przykład administratora systemu.

Kiedy istnieje jeden klucz dla całej kolumny dla wszystkich użytkowników (nawet jeśli nie dla wszystkich, ale dla klientów z ograniczonego zestawu), nie zawsze jest to dobre i prawidłowe. Dlatego zaczęto szyfrować od końca do końca, w systemie DBMS zaczęto rozważać opcje szyfrowania danych po stronie klienta i serwera i pojawiły się te same magazyny kluczy - osobne produkty zapewniające zarządzanie kluczami w systemie DBMS strona.

Bezpieczeństwo i DBMS: o czym należy pamiętać wybierając narzędzia bezpieczeństwa
Przykład takiego szyfrowania w MongoDB

Funkcje bezpieczeństwa w komercyjnych i otwartych systemach DBMS

funkcje
Typ
Polityka haseł
Audyt
Ochrona kodu źródłowego procedur i funkcji
RLS
Szyfrowanie

wyrocznia
Reklama
+
+
+
+
+

MsSql
Reklama
+
+
+
+
+

jatoba
Reklama
+
+
+
+
rozszerzenia

PostgreSQL
Darmowy
rozszerzenia
rozszerzenia
-
+
rozszerzenia

MongoDb
Darmowy
-
+
-
-
Dostępne tylko w MongoDB Enterprise

Tabela jest daleka od kompletności, ale sytuacja jest następująca: w produktach komercyjnych problemy bezpieczeństwa zostały rozwiązane od dawna, w open source z reguły dla bezpieczeństwa używane są jakieś dodatki, brakuje wielu funkcji , czasem trzeba coś dodać. Na przykład zasady haseł - PostgreSQL ma wiele różnych rozszerzeń (1, 2, 3, 4, 5), które realizują politykę haseł, ale moim zdaniem żadna z nich nie pokrywa wszystkich potrzeb krajowego segmentu korporacyjnego.

Co zrobić, jeśli nigdzie nie masz tego, czego potrzebujesz? Na przykład chcesz użyć określonego systemu DBMS, który nie ma funkcji wymaganych przez klienta.

Następnie możesz skorzystać z rozwiązań innych firm, które współpracują z różnymi systemami DBMS, na przykład Crypto DB lub Garda DB. Jeśli mówimy o rozwiązaniach z segmentu krajowego, to o GOST wiedzą lepiej niż w open source.

Druga możliwość to samodzielne napisanie czego potrzebujesz, zaimplementowanie dostępu do danych i szyfrowania w aplikacji już na poziomie proceduralnym. To prawda, że ​​​​z GOST będzie trudniej. Ale ogólnie rzecz biorąc, możesz w razie potrzeby ukryć dane, umieścić je w systemie DBMS, następnie odzyskać i odszyfrować w razie potrzeby, bezpośrednio na poziomie aplikacji. Jednocześnie od razu zastanów się, w jaki sposób zabezpieczysz te algorytmy w aplikacji. Naszym zdaniem należy to zrobić na poziomie DBMS, bo będzie to działać szybciej.

Raport ten został po raz pierwszy zaprezentowany o godz Spotkanie @Bazy Danych przez rozwiązania chmurowe Mail.ru. Patrzeć wideo inne występy i subskrybuj ogłoszenia o wydarzeniach na Telegramie Wokół Kubernetesa w Grupie Mail.ru.

Co jeszcze przeczytać na ten temat:

  1. Więcej niż Ceph: przechowywanie bloków w chmurze MCS.
  2. Jak wybrać bazę danych do projektu, aby nie musieć wybierać ponownie.

Źródło: www.habr.com

Dodaj komentarz