Nazywam się Denis Rozhkov, jestem kierownikiem rozwijania oprogramowania w firmie "Gazinformservice", w zespole produktu . Prawo i normy korporacyjne stawiają określone 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 uwierzytelnienie, zarządzanie dostępem do danych, zapewnienie integralności informacji w systemie oraz rejestrowanie zdarzeń bezpieczeństwa. Dlatego chcę opowiedzieć o kilku interesujących aspektach dotyczących bezpieczeństwa baz danych.
Artykuł przygotowano na podstawie wystąpienia na zorganizowanego . Jeśli nie chcesz czytać, możesz obejrzeć:

W artykule będą trzy części:
- Jak zabezpieczać połączenia.
- Czym jest audyt działań i jak rejestrować to, co dzieje się po stronie bazy danych i połączenia z nią.
- Jak zabezpieczać dane w samej bazie danych i jakie technologie są do tego dostępne.

Trzy komponenty bezpieczeństwa baz danych: ochrona połączeń, audyt działań i ochrona danych
Ochrona połączeń
Można łączyć się z bazą danych zarówno bezpośrednio, jak i pośrednio przez aplikacje internetowe. Zazwyczaj użytkownik ze strony biznesowej, czyli osoba pracująca z bazą danych, nie wchodzi w bezpośrednią interakcję z nią.
Zanim zaczniemy mówić o ochronie połączeń, musimy odpowiedzieć na ważne pytania, od których zależy, jak będą budowane działania związane z bezpieczeństwem:
- czy jeden użytkownik biznesowy jest równoważny jednemu użytkownikowi bazy danych;
- czy dostęp do danych bazy danych jest zapewniany tylko przez API, które kontrolujesz, czy też jest bezpośredni dostęp do tabel;
- czy baza danych znajduje się w oddzielnym chronionym segmencie, kto i jak wchodzi z nią w interakcję;
- czy wykorzystywany jest pooling/proxy i warstwy pośrednie, które mogą zmieniać informacje o tym, jak zbudowane jest połączenie i kto korzysta z bazy danych.
Teraz przyjrzyjmy się, jakie narzędzia można zastosować do ochrony połączeń:
- Stosuj rozwiązania klasy firewalla bazy danych. Dodatkowa warstwa ochrony, przynajmniej zwiększy przejrzystość tego, co dzieje się w bazie danych, maksymalnie — będziesz mógł zapewnić dodatkową ochronę danych.
- Używaj polityk haseł. Ich zastosowanie zależy od tego, jak jest zbudowana Twoja architektura. W każdym przypadku – jedno hasło w pliku konfiguracyjnym aplikacji internetowej, które łączy się z bazą danych, to za mało, aby zapewnić ochronę. Istnieje szereg narzędzi dla baz danych, które pozwalają kontrolować, czy użytkownik i hasło wymagają aktualizacji.
Więcej informacji na temat funkcji oceny użytkowników można znaleźć , a także można dowiedzieć się o MS SQL Vulnerability Assessment .
- Wzbogacaj kontekst sesji o potrzebne informacje. Jeśli sesja jest nieprzezroczysta, nie rozumiesz, kto działa w ramach bazy danych, można w ramach wykonywanej operacji dodać informacje o tym, kto, co i dlaczego robi. Te informacje można zobaczyć w audycie.
- Konfiguruj SSL, jeśli nie masz rozdzielenia sieciowego bazy danych od końcowych użytkowników, nie jest ona w osobnym VLAN. W takich przypadkach zawsze należy chronić kanał między użytkownikiem a samą bazą danych. Narzędzia ochrony są dostępne również w open source.
Jak to wpłynie na wydajność bazy danych?
Przyjrzymy się na przykładzie PostgreSQL, jak SSL wpływa na obciążenie CPU, zwiększenie opóźnień i zmniejszenie TPS, czy nie wykorzysta zbyt wielu zasobów po jego włączeniu.
Obciążamy PostgreSQL, korzystając z pgbench – to prosta aplikacja do uruchamiania testów wydajności. Wiele razy wykonuje sekwencję poleceń, możliwie w równoległych sesjach bazy danych, a następnie oblicza średnią prędkość transakcji.
Test 1 bez SSL i z użyciem SSL – połączenie jest nawiązywane przy 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 z użyciem SSL – wszystkie transakcje są wykonywane 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"Pozostałe ustawienia:
współczynnik skalowania: 1
tryb zapytań: proste
liczba klientów: 10
liczba wątków: 1
liczba transakcji na klienta: 5000
liczba transakcji faktycznie przetworzonych: 50000/50000Wyniki testowania:
BRAK SSL
SSL
Połączenie jest nawiązywane przy każdej transakcji
średnie opóźnienie
171.915 ms
187.695 ms
tps, w tym nawiązywanie połączeń
58.168112
53.278062
tps bez nawiązywania połączeń
64.084546
58.725846
CPU
24%
28%
Wszystkie transakcje są wykonywane w jednym połączeniu
średnie opóźnienie
6.722 ms
6.342 ms
tps, w tym nawiązywanie połączeń
1587.657278
1576.792883
tps bez nawiązywania połączeń
1588.380574
1577.694766
CPU
17%
21%
Przy niewielkich obciążeniach wpływ SSL jest porównywalny z błędem pomiarowym. Jeśli ilość przesyłanych danych jest bardzo duża, sytuacja może być inna. Jeśli zestawiamy jedno połączenie na każdą transakcję (co rzadko się zdarza, zwykle połączenia są dzielone między użytkowników), mamy wiele połączeń/rozłączeń, wpływ może być nieco większy. To znaczy, że ryzyko spadku wydajności może występować, jednak różnica nie jest na tyle duża, aby zrezygnować z ochrony.
Zwróć uwagę — istnieje znaczna różnica, jeśli porównamy tryby pracy: w ramach jednej sesji pracujesz lub w różnych. To jasne: stworzenie każdego połączenia wymaga zasobów.
Mieliśmy przypadek, gdy podłączaliśmy Zabbix w trybie zaufanym, czyli nie sprawdzaliśmy md5, nie było potrzeby autoryzacji. Potem klient poprosił o włączenie trybu autoryzacji md5. To przyczyniło się do dużego obciążenia CPU, wydajność spadła. Zaczęliśmy szukać sposobów optymalizacji. Jednym z możliwych rozwiązań problemu jest wdrożenie ograniczeń sieciowych, stworzenie osobnych VLAN dla SGBD, dodanie ustawień, aby było jasne, kto i skąd się łączy oraz rezygnacja z autoryzacji. Można także zoptymalizować ustawienia autoryzacji, aby zredukować koszty przy włączonej autoryzacji, jednak w ogólnym ujęciu stosowanie różnych metod autoryzacji wpływa na wydajność i należy brać to pod uwagę przy projektowaniu mocy obliczeniowych serwerów (sprzętu) dla SGBD.
Wniosek: w niektórych rozwiązaniach nawet niewielkie niuanse w autoryzacji mogą mieć znaczący wpływ na projekt i jest to niekorzystne, gdy staje się to jasne dopiero po wdrożeniu do produkcji.
Audyt działań
Audyt może dotyczyć nie tylko SGBD. Audyt to pozyskiwanie informacji o tym, co dzieje się w różnych segmentach. Może to być zarówno zapora baz danych, jak i system operacyjny, na którym opiera się SGBD.
W komercyjnych SGBD klasy Enterprise z audytem wszystko jest w porządku, w open source — nie zawsze. Oto co jest w PostgreSQL:
- default log — wbudowane logowanie;
- rozszerzenia: pgaudit — jeśli domyślne logowanie nie wystarcza, można skorzystać z dodatkowych ustawień, które rozwiązują część problemów.
Dodatkowe informacje do raportu w wideo:
Podstawowa rejestracja operatorów może być zapewniona standardowym narzędziem do rejestrowania z log_statement = all.
Jest to akceptowalne do monitorowania i innych rodzajów użycia, ale nie zapewnia poziomu szczegółowości zazwyczaj wymaganego do audytu.
Nie wystarczy mieć listę wszystkich operacji wykonywanych na bazie danych.
Powinna być także możliwość znalezienia konkretnych stwierdzeń, które są interesujące dla audytora.
Standardowe narzędzie do rejestrowania pokazuje to, co zażądał użytkownik, podczas gdy pgAudit skupia się na szczegółach tego, co się wydarzyło, gdy baza danych realizowała zapytanie.
Na przykład audytor może chcieć upewnić się, że konkretna tabela została utworzona w udokumentowanym oknie serwisowym.
Może się to wydawać prostym zadaniem do podstawowego audytu i grep’a, ale co, jeśli staniecie przed czymś takim (celowo złożonym) przykładem:
DO $$
BEGIN
WYKONAJ ‘UTWÓRZ TABELĘ import’ || ‘ant_table (id INT)’;
KONIEC $$;
Standardowe rejestrowanie da ci to:
LOG: statement: DO $$
BEGIN
WYKONAJ ‘UTWÓRZ TABELĘ import’ || ‘ant_table (id INT)’;
KONIEC $$;
Wydaje się, że zlokalizowanie interesującej tabeli może wymagać pewnej wiedzy o kodzie w przypadku, gdy tabele są tworzone dynamicznie.
To nie jest idealne, ponieważ lepiej byłoby po prostu szukać po nazwie tabeli.
Tu przyda się pgAudit.
Dla tego samego wejścia wygeneruje ten output w logu:
AUDYT: SESSION,33,1,FUNCTION,DO,,,‘DO $$
BEGIN
WYKONAJ ‘UTWÓRZ TABELĘ import’ || ‘ant_table (id INT)’;
KONIEC $$;"
AUDYT: SESSION,33,2,DDL,UTWÓRZ TABELĘ,TABELA,public.important_table,UTWÓRZ TABELĘ important_table (id INT)
Rejestrowany jest nie tylko blok DO, ale i pełen tekst UTWÓRZ TABELĘ z typem operatora, typem obiektu i pełną nazwą, co ułatwia wyszukiwanie.
Podczas rejestrowania operatorów SELECT i DML pgAudit można skonfigurować do rejestrowania oddzielnego wpisu dla każdej relacji, do której się odnosi w operatorze.
Nie wymaga to analizy składni, aby znaleźć wszystkie operatory, które dotyczą konkretnej tabeli ()».
Jak to wpłynie na wydajność bazy danych?
Zróbmy testy z włączonym pełnym audytem i zobaczmy, jak wpłynie to na wydajność PostgreSQL. Włączmy maksymalne rejestrowanie Bazy Danych dla wszystkich parametrów.
W pliku konfiguracyjnym prawie nic nie zmieniamy, z ważnych rzeczy — włączamy tryb debug5, aby uzyskać maksymalną ilość informacji.
postgresql.conf
log_destination = ‘stderr’
logging_collector = on
log_truncate_on_rotation = on
log_rotation_age = 1d
log_rotation_size = 10MB
log_min_messages = debug5
log_min_error_statement = debug5
log_min_duration_statement = 0
debug_print_parse = on
debug_print_rewritten = on
debug_print_plan = on
debug_pretty_print = włączone
log_checkpoints = włączone
log_connections = włączone
log_disconnections = włączone
log_duration = włączone
log_hostname = włączone
log_lock_waits = włączone
log_replication_commands = włączone
log_temp_files = 0
log_timezone = 'Europa/Moskwa'
Na bazie danych PostgreSQL z parametrami 1 CPU, 2,8 GHz, 2 GB RAM, 40 GB HDD przeprowadzamy trzy testy obciążeniowe, używając 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 benchmarkWyniki testowania:
Bez logowania
Z logowaniem
Łączny czas wypełnienia 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
Transakcji/sek
123
54
Średnie opóźnienie
405 ms
925 ms
Test 2 (150 połączeń przy 100 możliwych)
Liczba transakcji w ciągu 10 minut
81727
31429
Transakcji/sek
136
52
Średnie opóźnienie
550 ms
1432 ms
O rozmiarach
Rozmiar bazy danych
2251 MB
2262 MB
Rozmiar logów bazy danych
0 MB
4587 MB
W rezultacie: audyt całkowity — to nie jest zbyt dobrze. Volume danych z audytu będzie równy lub większy niż dane w samej bazie danych. Taki przyrost logów, który generuje się podczas pracy z DBMS, jest powszechnym problemem na produkcji.
Patrzymy na inne parametry:
- Prędkość nie zmienia się znacząco: bez logowania — 43,74 sek, z logowaniem — 53,23 sek.
- Wydajność RAM i CPU będzie spadać, ponieważ należy utworzyć plik audytowy. To także jest zauważalne na produkcji.
Przy zwiększeniu liczby połączeń, naturalnie, wyniki będą się nieco pogarszać.
W korporacjach z audytem jest jeszcze trudniej:
- jest dużo danych;
- audyt nie tylko przez syslog w SIEM, ale również w plikach: na wszelki wypadek, jeśli coś stanie się z syslog, musi być blisko bazy skrypt, w którym zachowają się dane;
- do audytu potrzebna jest osobna półka, aby nie obciążać I/O dysków, ponieważ zajmuje ona dużo miejsca;
- czasami pracownikom z działu bezpieczeństwa potrzebne są wszędzie normy, wymagają identyfikacji wg standardów.
Ograniczenie dostępu do danych
Przyjrzymy się technologiom, które stosują do ochrony danych i dostępu do nich w komercyjnych DBMS i open source.
Czego ogólnie można użyć:
- Szyfrowanie i obfuskacja procedur oraz funkcji (Wrapping) — czyli osobne narzędzia i pomoce, które przekształcają czytelny kod w nieczytelny. Prawda, później nie można go ani zmienić, ani zrefaktoryzować z powrotem. Takie podejście czasami jest wymagane przynajmniej po stronie DBMS — logika ograniczeń licencyjnych lub logika autoryzacji jest szyfrowana dokładnie na poziomie procedur i funkcji.
- Ograniczenie widoczności danych wierszy (RLS) to sytuacja, w której różni użytkownicy widzą tę samą tabelę, ale różne wiersze w niej, to znaczy, że pewnym osobom nie można pokazywać niektórych danych na poziomie wierszy.
- Edycja wyświetlanych danych (Masking) to proces, w którym użytkownicy w jednej kolumnie tabeli widzą albo dane, albo same gwiazdki, co oznacza, że dla niektórych użytkowników informacje będą zablokowane. Technologia ta określa, które dane są dostępne dla poszczególnych użytkowników w zależności od poziomu dostępu.
- Rozgraniczenie dostępu Security DBA/Application DBA/DBA dotyczy głównie ograniczenia dostępu do samej bazy danych, co pozwala na oddzielenie pracowników odpowiedzialnych za bezpieczeństwo od administratorów bazy danych i administratorów aplikacji. W przypadku technologii open source jest ich niewiele, ale w komercyjnych bazach danych jest ich dużo. Są one potrzebne, gdy wielu użytkowników ma dostęp do serwerów.
- Ograniczenie dostępu do plików na poziomie systemu plików. Można przydzielać prawa i uprawnienia dostępu do katalogów, aby każdy administrator miał dostęp tylko do potrzebnych danych.
- Dostęp mandatu i oczyszczanie pamięci to technologie, które stosuje się rzadko.
- Szyfrowanie end-to-end w samej bazie danych to szyfrowanie po stronie klienta z zarządzaniem kluczami po stronie serwera.
- Szyfrowanie danych. Na przykład szyfrowanie kolumnowe — kiedy używasz mechanizmu, który szyfruje konkretną kolumnę w bazie danych.
Jak to wpływa na wydajność bazy danych?
Zobaczmy na przykładzie szyfrowania kolumnowego w PostgreSQL. Istnieje tam moduł pgcrypto, który pozwala na przechowywanie wybranych pól w postaci zaszyfrowanej. To jest przydatne, gdy tylko niektóre dane mają wartość. Aby odczytać zaszyfrowane pola, klient przesyła klucz deszyfrujący, serwer odszyfrowuje dane i zwraca je klientowi. Bez klucza nikt nie może zrobić nic z vašymi danymi.
Przeprowadzimy test z pgcrypto.. Stworzymy tabelę z zaszyfrowanymi danymi i z danymi zwykłymi. Poniżej polecenia do tworzenia tabel, a w samej pierwszej linii przydatne polecenie — utworzenie samego rozszerzenia z rejestracją bazy danych:
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'));Spróbujmy następnie wykonać zapytanie z każdej tabeli i przyjrzymy się czasom wykonania.
Zapytanie z tabeli bez zastosowania funkcji szyfrowania:
psql -c "timing" -c "select * from t1 limit 1000;" "host=192.168.220.129 dbname=taskdb
user=postgres sslmode=disable" > 1.txtStoper włączony.
id | text1 | text2
——+——-+——-
1 | 1 | 1
2 | 2 | 2
3 | 3 | 3
…
997 | 997 | 997
998 | 998 | 998
999 | 999 | 999
1000 | 1000 | 1000
(1000 wierszy)
Czas: 1,386 ms
Zapytanie z tabeli z zastosowaniem funkcji 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.txtStoper włączony.
id | decrypt | decrypt
——+—————+————
1 | x31 | x31
2 | x32 | x32
3 | x33 | x33
…
999 | x393939 | x393939
1000 | x31303030 | x31303030
(1000 wierszy)
Czas: 50,203 ms
Wyniki testowania:
Bez szyfrowania
Pgcrypto (decryption)
Zapytanie 1000 wierszy
1,386 ms
50,203 ms
CPU
15%
35%
RAM
+5%
Szyfrowanie ma znaczący wpływ na wydajność. Widać, że czas wzrósł, ponieważ operacje deszyfrowania zaszyfrowanych danych (a deszyfrowanie jest zazwyczaj owinięte w twoją logikę) wymagają znacznych zasobów. Oznacza to, że pomysł szyfrowania wszystkich kolumn zawierających dane może prowadzić do spadku wydajności.
Jednak szyfrowanie nie jest panaceum, które rozwiązuje wszystkie problemy. Odszyfrowane dane i klucz deszyfrujący w trakcie deszyfrowania i przesyłania danych znajdują się na serwerze. Dlatego klucze mogą być przechwycone przez osobę mającą pełny dostęp do serwera bazy danych, np. przez administratora systemu.
Kiedy dla całej kolumny dla wszystkich użytkowników jest jeden klucz (nawet jeśli nie dla wszystkich, a dla klientów ograniczonego zestawu), to nie zawsze jest to dobre i właściwe. Dlatego zaczęto stosować szyfrowanie end-to-end, w systemach DB zaczęto rozważać opcje szyfrowania danych ze strony klienta i serwera, powstały te tak zwane magazyny kluczy — oddzielne produkty, które zapewniają zarządzanie kluczami po stronie systemu DB.

Środki bezpieczeństwa w komercyjnych i open source systemach DB
Funkcje
Typ
Polityka haseł
Audyt
Ochrona kodu źródłowego procedur i funkcji
RLS
Szyfrowanie
Oracle
Komercyjna
+
+
+
+
+
MsSql
Komercyjna
+
+
+
+
+
Komercyjna
+
+
+
+
rozszerzenia
PostgreSQL
Darmowe
rozszerzenia
rozszerzenia
—
+
rozszerzenia
MongoDb
Darmowe
—
+
—
—
Dostępne tylko w MongoDB Enterprise
Tabela nie jest pełna, ale sytuacja jest taka: w produktach komercyjnych problemy bezpieczeństwa są rozwiązane od dawna, w open source zwykle korzysta się z różnego rodzaju nadbudów, brakuje wielu funkcji, czasem trzeba coś dopisać. Na przykład polityki haseł — w PostgreSQL jest wiele różnych rozszerzeń., , , , ), które wdrażają polityki haseł, ale wszystkie potrzeby krajowego segmentu korporacyjnego, moim zdaniem, żadna z nich nie spełnia.
Co zrobić, jeśli nigdzie nie ma tego, czego potrzebujesz? Например, хочется использовать определенную СУБД, в которой нет функций, которые требует заказчик.
W takim razie można użyć zewnętrznych rozwiązań, które współpracują z różnymi DBMS, na przykład „Krypto Baza” lub „Garda Baza”. Jeśli mowa o rozwiązaniach z krajowego segmentu, to tam znają GOSTy lepiej niż w open source.
Drugą opcją jest samodzielne napisanie tego, czego potrzebujesz, wdrożenie na poziomie procedur dostępu do danych i szyfrowania w aplikacji. Prawda, GOST będzie trudniejszy. Ale ogólnie — możesz ukryć dane, jak chcesz, umieścić w DBMS, a potem je wyciągnąć i odszyfrować, jak chcesz, bezpośrednio na poziomie aplikacji. Przy tym od razu pomyśl, jak będziesz chronić te algorytmy na poziomie aplikacji. Naszym zdaniem, powinno się to robić na poziomie DBMS, ponieważ wtedy będzie działać szybciej.
Ta prezentacja pierwszy raz została zaprezentowana na by Mail.ru Cloud Solutions. Zobaczinne prezentacje i subskrybuj zapowiedzi wydarzeń w Telegramie .
Co jeszcze przeczytać na ten temat:
- .
- .
Źródło: habr.com

