Zabezpieczanie MariaDB i MySQL: kroki po instalacji

Opublikowano 14 min czytania

Po instalacji pakietu baza danych nie jest jeszcze bezpieczna. Ten poradnik przechodzi mysql_secure_installation krok po kroku, wyjaśnia pytanie o unix_socket i pokazuje własne konto dla każdej aplikacji.

Świeżo zainstalowana baza danych na serwerze root rzadko bywa tak dziurawa, jak twierdzą starsze poradniki, i rzadko tak bezpieczna, jak sugeruje system pakietów. Pomiędzy tymi biegunami leżą dokładnie te czynności, które opisuje ten artykuł: co naprawdę załatwia mysql_secure_installation, dlaczego na współczesnych systemach na pytanie o unix_socket odpowiada się inaczej niż w 2015 roku, jak wygląda konto aplikacji z minimalnymi uprawnieniami i jak trzymać hasła z dala od listy procesów.

Wszystkie polecenia wykonujesz jako root. Jeśli pracujesz na zwykłym użytkowniku, dopisz z przodu sudo. Pokazane wyniki pochodzą z Debiana 13 i Debiana 12 oraz Ubuntu 24.04 i Ubuntu 22.04.

Punkt wyjścia: który pakiet działa w której dystrybucji

Pierwsza pułapka czeka jeszcze przed pierwszym krokiem zabezpieczającym. Debian od lat nie dostarcza własnego pakietu o nazwie mysql-server. Nazwa istnieje tam już tylko jako pakiet wirtualny bez własnej wersji, widoczny wyłącznie przez zależności pakietu mariadb-server:

apt-cache policy mysql-server
mysql-server:
  Installed: (none)
  Candidate: (none)
  Version table:

To porównanie wersji daje więc wynik tylko na Ubuntu, i to 8.0.46 zarówno na 24.04, jak i na 22.04. Na Debianie użyj zamiast tego apt-cache showpkg mysql-server, które pokazuje wirtualny charakter tej nazwy.

Na Debianie bazą danych jest zatem zawsze MariaDB. Na Ubuntu dostępne są obie i musisz wybrać. Wersje w aktualnych dystrybucjach:

Dystrybucjamariadb-servermysql-server
Debian 1311.8tylko nazwa wirtualna, brak wersji
Debian 1210.11tylko nazwa wirtualna, brak wersji
Ubuntu 24.0410.118.0.46
Ubuntu 22.0410.68.0.46

Instalacja przebiega jak zwykle:

apt update
apt install -y mariadb-server

O tym, czy naprawdę trafiłeś na oczekiwaną wersję, nie decyduje sama wersja pakietu, tylko działający serwer:

mariadb -e "SELECT @@version, @@version_comment;"

Druga pułapka to nazwa programu. Od MariaDB 11.0 nazwy zgodne z MySQL nie leżą już w pakiecie głównym, tylko w mariadb-client-compatmariadb-server-compat. Na Debianie 13 polecenie mysql --version może więc albo odpowiedzieć ostrzeżeniem, albo w ogóle nie istnieć:

mysql: Deprecated program name. It will be removed in a future release, use '/usr/bin/mariadb' instead
bash: mysql: command not found

Jeśli piszesz skrypty, które mają działać na wszystkich czterech systemach, używaj w MariaDB konsekwentnie mariadb, mariadb-dumpmariadb-secure-installation. Te nazwy istnieją od MariaDB 10.5, a więc także na Ubuntu 22.04.

mysql_secure_installation krok po kroku

Narzędzie nazywa się inaczej w zależności od serwera. W MariaDB kanoniczna nazwa brzmi mariadb-secure-installation, w MySQL 8.0 pozostaje mysql_secure_installation. Stara nazwa nadal istnieje w MariaDB na Debianie 12 oraz na Ubuntu 24.04 i 22.04 jako dowiązanie symboliczne, na Debianie 13 natomiast już nie: tam jest wyłącznie mariadb-secure-installation.

Warto zerknąć na opcje, bo program przyjmuje między innymi --defaults-file, --socket--protocol. To zestawienie znajdziesz jednak wyłącznie w podręczniku:

man mariadb-secure-installation

Opcji --help bowiem nie ma. Skrypt w ogóle nie analizuje argumentów, po cichu połyka nieznany przełącznik i od razu uruchamia tryb interaktywny. Jeśli brakuje przy tym prawdziwej konsoli, na przykład w potoku albo w kontenerze bez terminala, skrypt w nieskończoność powtarza pytanie o hasło i nigdy sam nie kończy pracy. Dlatego wywołuj go bez argumentów i w interaktywnym shellu:

mariadb-secure-installation

Na Debianie 13 skrypt poprzedza cały przebieg wyraźną uwagą:

NOTE: MariaDB is secure by default in Debian. Running this script is useless at best,
and misleading at worst. This script will be removed in a future MariaDB release in Debian.

Debian uznaje więc uruchamianie tego skryptu za zbędne i usunie go w przyszłym wydaniu, co możesz doczytać w /usr/share/doc/mariadb-server/README.Debian.gz. Kolejne kroki i tak warto znać, bo pokazują, co jest sprawdzane i dlaczego na aktualnym systemie prawie nic nie trzeba już zmieniać.

Przebieg zaczyna się od pytania o hasło dla root. Dokładne brzmienie zależy od wersji skryptu:

Enter current password for root (enter for none):

Nowsze wersje pytają zamiast tego:

Enter root user password or leave blank:

W obu przypadkach chodzi o to samo. W świeżej instalacji hasła nie ma, więc po prostu naciśnij Enter.

Potem następują właściwe decyzje. Kolejność i brzmienie różnią się między MariaDB a MySQL, ale treściowo chodzi o te same pięć punktów.

Pytanie o unix_socket

W MariaDB jako następne pojawia się:

Switch to unix_socket authentication [Y/n]

To pytanie wprowadza zamęt, bo na wszystkich omawianych tu systemach odpowiedź dawno już zapadła. Od MariaDB 10.4 konto root@localhost jest domyślnie chronione wtyczką unix_socket, i dotyczy to zarówno 10.6 na Ubuntu 22.04, jak i 11.8 na Debianie 13. W MySQL 8.0 odpowiednikiem jest auth_socket, a kreator mówi to wprost:

Skipping password set for root as authentication with auth_socket is used by default.

W praktyce oznacza to tyle: kto jest zalogowany jako użytkownik systemowy root, wchodzi do bazy poleceniem mariadb bez hasła. Kto nim nie jest, nie wejdzie w ogóle, nawet z poprawnym hasłem. To nie jest wada, tylko mocniejszy wariant. Nie ma żadnego hasła, które mogłoby wyciec z kopii zapasowej, z pliku konfiguracyjnego albo ze zrzutu ekranu. Odpowiedź na to pytanie brzmi więc zostaw tak, jak jest, a dodatkowe hasło root na pojedynczym serwerze aplikacyjnym jest zbędne.

Stan sprawdzisz wyłącznie na prawdziwej tabeli. Nasuwające się zapytanie SELECT user, host, plugin FROM mysql.user prowadzi tu bowiem na manowce: przy root pokazuje wartość mysql_native_password, choć w rzeczywistości działa unix_socket. W MariaDB od 10.4 mysql.user jest już tylko widokiem na mysql.global_priv, a ten widok zna na konto tylko jedną metodę uwierzytelniania. Kto na nim polega, błędnie uzna serwer za uwierzytelniany hasłem. Pełna reguła znajduje się w polu JSON Priv prawdziwej tabeli:

mariadb -e "SELECT User, Host, JSON_DETAILED(Priv) FROM mysql.global_priv;"

Dla root na localhost widnieje tam w MariaDB mniej więcej to:

{"plugin":"mysql_native_password","authentication_string":"invalid","auth_or":[{},{"plugin":"unix_socket"}]}

Pierwszy wpis to metoda hasłowa wymierzona w bezużyteczny skrót łańcucha invalid, którego nikt nie trafi. Drugi wpis pod auth_or to faktycznie działające uwierzytelnianie przez socket. Zwięźlej odpytasz oba tak:

mariadb -e "SELECT User, Host, JSON_VALUE(Priv,'$.plugin') AS plugin, JSON_QUERY(Priv,'$.auth_or') AS auth_or FROM mysql.global_priv;"

Przy rootlocalhost oczekujesz unix_socket, jeśli nie w kolumnie plugin, to pod auth_or. Jeśli jest tam wyłącznie metoda hasłowa i nic pod auth_or, konto pracuje czysto na haśle. W MySQL 8.0 mysql.global_priv nie istnieje, tam mysql.user jest prawdziwą tabelą, a kolumna plugin musi pokazywać auth_socket. Z widoku MariaDB nadal zresztą przeczytasz dane, ale już do niego nie zapiszesz.

Przestawiasz to tylko wtedy, gdy jakieś narzędzie bezwzględnie potrzebuje hasła, na przykład monitoring, który nie działa jako root. Sensowniej jest wtedy jednak założyć drugie konto administracyjne, zamiast przerabiać konto root. W MariaDB od 11.6, a więc także w 11.8 na Debianie 13, konto można dodatkowo powiązać z konkretnym użytkownikiem systemowym:

CREATE USER 'dbadmin'@'localhost' IDENTIFIED VIA unix_socket AS 'deploy';

Dzięki temu użytkownik systemowy deploy może logować się jako użytkownik bazy dbadmin, a nazwy nie muszą się pokrywać. Na Debianie 12 i w wersjach Ubuntu łańcuch po AS jest jeszcze ignorowany, tam użytkownik systemowy i użytkownik bazy muszą nazywać się tak samo.

Cztery pozostałe pytania

Reszta nie budzi wątpliwości i wszędzie odpowiadasz Tak: usuń anonimowych użytkowników, zablokuj zdalne logowanie na konto root, usuń bazę test razem z uprawnieniami, przeładuj tabele uprawnień. Na aktualnym Debianie albo Ubuntu anonimowi użytkownicy i baza testowa i tak zwykle nie są zakładane, więc skrypt po prostu zgłasza, że nie było nic do zrobienia.

W MySQL 8.0 dochodzi pytanie, którego MariaDB nie zna:

Would you like to setup VALIDATE PASSWORD component?

Ten komponent wymusza minimalne wymagania wobec wszystkich haseł ustawianych w przyszłości. Bywa przydatny, gdy konta zakłada kilka osób. Bywa uciążliwy, gdy skrypt provisioningu generuje losowe hasła, w których przypadkiem zabrakło znaku specjalnego. Wtedy skrypt przerywa pracę komunikatem:

ERROR 1819 (HY000): Your password does not satisfy the current policy requirements

Jeśli go włączasz, wcześniej dostosuj do niego generator haseł. Domyślny poziom to MEDIUM i wymaga co najmniej ośmiu znaków, wielkich i małych liter, cyfry oraz znaku specjalnego.

Gdy coś pójdzie nie tak: powrót do bazy danych

Najczęstszy sposób na zatrzaśnięcie sobie drzwi to dobrze pomyślana zmiana z unix_socket na hasło, które potem ginie. Albo odwrotnie: stary skrypt tworzy na nowo /etc/mysql/debian.cnf i nagle nic już do siebie nie pasuje. Komunikaty błędów, których wtedy szukasz, brzmią:

ERROR 1698 (28000): Access denied for user 'root'@'localhost'
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
ERROR 1524 (HY000): Plugin 'unix_socket' is not loaded

Błąd 1698 znaczy: konto oczekuje unix_socket, ale ty nie jesteś właściwym użytkownikiem systemowym. Często wystarczy dopisać z przodu sudo. Błąd 1045 znaczy: oczekiwane jest hasło, a twoje się nie zgadza.

Jeśli w ogóle nie możesz się już dostać do bazy, uruchom serwer bez sprawdzania uprawnień. W MariaDB da się to zrobić czysto przez zmienną środowiskową, którą analizuje dostarczona jednostka systemd, całkiem bez edytowania plików z pakietu:

systemctl stop mariadb
systemctl set-environment MYSQLD_OPTS="--skip-grant-tables --skip-networking"
systemctl start mariadb

Teraz połącz się poleceniem mariadb -u root i przywróć stan wyjściowy. Ważne jest wcześniejsze FLUSH PRIVILEGES, bo bez wczytanych tabel uprawnień ALTER USER kończy się błędem:

FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED VIA unix_socket;

Potem koniecznie posprzątaj, bo inaczej serwer po każdym restarcie startuje bez ochrony:

systemctl stop mariadb
systemctl unset-environment MYSQLD_OPTS
systemctl start mariadb

W MySQL 8.0 na Ubuntu ta droga nie działa, bo jednostka nie analizuje takiej zmiennej, a ALTER USER pod --skip-grant-tables ma dodatkowe pułapki. Tam użyj pliku startowego. Musi on leżeć w katalogu, na który AppArmor pozwala procesowi serwera, inaczej start kończy się komunikatem Can't open file. Katalog /var/lib/mysql-files jest dozwolony, /tmp nie:

systemctl stop mysql
echo "ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket;" > /var/lib/mysql-files/reset.sql
chown mysql:mysql /var/lib/mysql-files/reset.sql
systemctl edit mysql

Do edytora wpisz nadpisanie polecenia startowego; pusty pierwszy wiersz jest konieczny, żeby skasować wartość oryginalną:

[Service]
ExecStart=
ExecStart=/usr/sbin/mysqld --init-file=/var/lib/mysql-files/reset.sql

Po systemctl daemon-reload i starcie konto jest zresetowane. Następnie usuń nadpisanie poleceniem systemctl revert mysql i skasuj plik. Jeśli prowadzisz bazy danych na osobnych systemach, uzupełniające wskazówki dotyczące zabezpieczenia dostępu znajdziesz we wpisie Zabezpieczanie serwera SSH.

Własny użytkownik dla każdej aplikacji zamiast konta root

Najskuteczniejszy krok nie występuje w żadnym kreatorze. Aplikacje nie mogą łączyć się jako root i nie potrzebują też GRANT ALL. Typowa aplikacja webowa czyta i zapisuje wiersze, nie zakłada baz danych i nie czyta plików z serwera.

CREATE DATABASE shopdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'shopapp'@'localhost' IDENTIFIED BY 'TutajDlugieLosoweHaslo';
GRANT SELECT, INSERT, UPDATE, DELETE ON shopdb.* TO 'shopapp'@'localhost';

Ważne są tu trzy rzeczy. Po pierwsze kropka w shopdb.* zamiast *.*: uprawnienia na *.* są uprawnieniami globalnymi i obejmują także mysql oraz information_schema. Po drugie zapis @'localhost' zamiast @'%': dzięki niemu konto działa wyłącznie lokalnie, nawet jeśli port kiedyś stanie otworem. Po trzecie brakuje WITH GRANT OPTION, bo konto, które może przekazywać uprawnienia, jest faktycznie administratorem.

Zmiany schematu przeprowadzasz wtedy na drugim koncie, używanym wyłącznie przy deployu:

CREATE USER 'shopmigrate'@'localhost' IDENTIFIED BY 'InneDlugieLosoweHaslo';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, INDEX, REFERENCES ON shopdb.* TO 'shopmigrate'@'localhost';

Brzmi to jak dodatkowa robota i jest dokładnie tym miejscem, w którym SQL injection w aplikacji kończy się irytującym wyciekiem danych zamiast całkowitą stratą. Konto bez DROP nie skasuje żadnej tabeli.

Kontrolujesz to nie po brzmieniu polecenia GRANT, tylko po wyniku:

SHOW GRANTS FOR 'shopapp'@'localhost';

Oczekujesz dokładnie dwóch wierszy: GRANT USAGE ON *.*, które oznacza wyłącznie prawo do zalogowania się i nie daje żadnego dostępu do danych, oraz wiersza z czterema uprawnieniami na shopdb.*. Jeśli pojawi się tam ALL PRIVILEGES ON *.*, kropka stała w złym miejscu. Druga, bardziej wymowna próba to zalogowanie się nowym kontem i wykonanie SHOW DATABASES;. Widoczne mogą być tylko information_schemashopdb.

Częsty błąd przy zakładaniu konta:

ERROR 1396 (HY000): Operation CREATE USER failed for 'shopapp'@'localhost'

Prawie zawsze znaczy to, że konto już istnieje, często jako pozostałość po wcześniejszej próbie. DROP USER 'shopapp'@'localhost'; i jeszcze raz od początku.

bind-address: gdzie baza danych w ogóle nasłuchuje

We wszystkich czterech dystrybucjach baza danych po instalacji pakietu nasłuchuje tylko na 127.0.0.1. Wiersz ten znajduje się w MariaDB w /etc/mysql/mariadb.conf.d/50-server.cnf, a w MySQL 8.0 w /etc/mysql/mysql.conf.d/mysqld.cnf. Sprawdź, zamiast zgadywać:

grep -R "bind-address" /etc/mysql/

W MySQL 8.0 jest drugi wiersz, który chętnie umyka uwadze: mysqlx-bind-address steruje protokołem X na porcie 33060. Jeśli zmienisz tylko bind-address, w pewnych okolicznościach zamkniesz dostęp jedynie połowicznie albo zostawisz drugi port otwarty.

Najbardziej wiarygodnej odpowiedzi nie udzieli plik konfiguracyjny, tylko kernel:

ss -lntp
LISTEN 0 80 127.0.0.1:3306 0.0.0.0:* users:(("mariadbd",pid=712,fd=22))

Jeśli widnieje tam 0.0.0.0:3306 albo *:3306, usługa jest osiągalna w sieci. Uzupełniająco serwer poda swój własny obraz sytuacji:

mariadb -e "SELECT @@bind_address, @@port, @@skip_networking;"

Powszechne są tu dwie pułapki. Pierwsza: pliki w mariadb.conf.d czytane są w kolejności alfabetycznej i wygrywa wpis przeczytany jako ostatni. Dlatego własną zmianę zapisz w pliku o nazwie 99-wlasne.cnf, a nie 10-wlasne.cnf. Właśnie na tym rozbija się większość relacji, w których MariaDB rzekomo ignoruje bind-address. Zaleta własnego pliku: aktualizacje pakietów nie pytają o rozwiązanie konfliktu, bo plik dostarczony z pakietem pozostaje nietknięty.

Druga pułapka: jeśli dostęp sieciowy w ogóle nie jest ci potrzebny, idź o krok dalej niż bind-address i ustaw skip-networking. Wtedy nie ma już żadnego portu TCP, zostaje wyłącznie socket uniksowy. To właściwe ustawienie dla standardowego przypadku, czyli aplikacji webowej i bazy danych na tym samym serwerze, ale kosztuje nerwy, gdy aplikacja ma w konfiguracji wpisane 127.0.0.1 zamiast localhost: przy 127.0.0.1 biblioteki klienckie wymuszają TCP.

Dostęp z zewnątrz tylko wtedy, gdy jest naprawdę potrzebny

Port bazy danych w otwartej sieci zostaje znaleziony w ciągu kilku godzin i potem jest bez ustanku przeczesywany. Najlepszą odpowiedzią na pytanie o dostęp z zewnątrz jest więc jego uniknięcie. Do sporadycznej konserwacji wystarczy tunel SSH, który na twoim komputerze przekierowuje lokalny port na socket bazy po drugiej stronie. Narzędzie bazodanowe łączy się wtedy z 127.0.0.1, a serwer niczego w sieci nie otwiera.

Dla trwałych połączeń między kilkoma serwerami czystym rozwiązaniem jest tunel WireGuard. Baza danych nasłuchuje wtedy wyłącznie na adresie tunelu, a nie na publicznym adresie IP.

Jeśli otwarty port jest jednak konieczny, cztery środki muszą zadziałać razem. Serwer nasłuchuje na dokładnie jednym adresie wewnętrznym. Firewall przepuszcza tylko znany adres źródłowy. Konto bazy danych jest związane z tym samym adresem, czyli 'shopapp'@'10.0.0.5', a nigdy 'shopapp'@'%'. I połączenie jest wymuszane jako szyfrowane:

ALTER USER 'shopapp'@'10.0.0.5' REQUIRE SSL;

Przy testowaniu z zewnątrz napotkasz dwa błędy, które często bywają mylone. Pierwszy oznacza, że nikt nie odpowiada, a więc firewall albo bind-address:

ERROR 2003 (HY000): Can't connect to MySQL server on '203.0.113.10:3306' (110)

Drugi oznacza, że serwer odpowiada i świadomie odrzuca połączenie, brakuje więc konta pasującego do tego adresu źródłowego:

ERROR 1130 (HY000): Host '203.0.113.55' is not allowed to connect to this MariaDB server

Lokalnie natomiast klasykiem jest sytuacja, w której usługa po prostu nie działa:

ERROR 2002 (HY000): Can't connect to local server through socket '/run/mysqld/mysqld.sock' (2)

Hasła nie w wierszu poleceń

Wywołanie mariadb -u shopapp -pTajne123 działa, a mimo to jest błędem. Serwer sam to mówi:

Warning: Using a password on the command line interface can be insecure.

Stoją za tym dwa powody. Po pierwsze wiersz ląduje w historii shella. Po drugie wiersz poleceń procesu jest na standardowym systemie widoczny przez ps dla każdego zalogowanego użytkownika. Na serwerze z wieloma klientami albo wieloma usługami to wyciek hasła bez najmniejszego wysiłku.

Właściwą drogą dla ludzi jest -p bez doklejonej wartości. Wtedy pytanie pada interaktywnie i nic nie zostaje w historii:

mariadb -u shopapp -p shopdb

Właściwą drogą dla skryptów i cronjobów jest plik opcji z wąskimi uprawnieniami. Zakładasz go od razu z poprawnym trybem:

install -m 600 /dev/null /root/.my.cnf

Zawartość:

[client]
user=backup
password=TutajDlugieLosoweHaslo

Od tej pory każdy klient sam znajdzie dane logowania. Do rozdzielonych zadań zakładasz kilka plików i wskazujesz je wprost. Obowiązuje przy tym reguła, na której wielu się potyka: --defaults-extra-file--defaults-file muszą być pierwszą opcją wywołania, inaczej zostaną bez słowa zignorowane.

mariadb-dump --defaults-extra-file=/root/.my-backup.cnf --single-transaction shopdb

Zmienna środowiskowa MYSQL_PWD nie jest rozwiązaniem. Znajduje się w /proc i przez to jest widoczna podobnie jak wiersz poleceń. W MySQL 8.0 istnieje dodatkowo mysql_config_editor, który zapisuje plik ~/.mylogin.cnf. Zawartość jest zaciemniona, ale nie zaszyfrowana, a MariaDB tego narzędzia nie zna. W środowiskach mieszanych zwykły plik opcji z trybem 600 jest pewniejszym wyborem.

Ostatni punkt dotyczy kopii zapasowych. Zrzut zawiera wszystko, co widzi aplikacja, a konto backupu nie potrzebuje do tego uprawnień do zapisu. Dla mariadb-dump--single-transaction z reguły wystarczy:

GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES ON shopdb.* TO 'backup'@'localhost';
GRANT PROCESS ON *.* TO 'backup'@'localhost';

Odbiór: po czym poznasz, że wszystko trzyma

Polecenie, które przechodzi bez błędu, niczego nie dowodzi. Te sześć prób dowodzi czegoś konkretnego:

  1. Konto root używa uwierzytelniania przez socket: mariadb -e "SELECT User, Host, JSON_VALUE(Priv,'$.plugin') AS plugin, JSON_QUERY(Priv,'$.auth_or') AS auth_or FROM mysql.global_priv;" pokazuje przy root wartość unix_socket, albo w kolumnie plugin, albo pod auth_or. Widok mysql.user się do tego nie nadaje, podaje tylko pierwszą metodę. W MySQL 8.0 jest odwrotnie: decyduje kolumna pluginmysql.user i musi tam być auth_socket.
  2. Nie ma anonimowych kont: mariadb -e "SELECT user, host FROM mysql.user WHERE user = '';" zwraca pusty zbiór wyników.
  3. Baza testowa zniknęła: mariadb -e "SHOW DATABASES LIKE 'test';" nie zwraca niczego.
  4. Port jest zamknięty: ss -lntp pokazuje dla 3306 albo w ogóle nic, albo wyłącznie 127.0.0.1.
  5. Konto aplikacji jest ograniczone: po zalogowaniu się na nie SHOW DATABASES; pokazuje tylko własną bazę, a DROP TABLE kończy się błędem.
  6. Żadnego hasła jawnym tekstem w obiegu: grep -rs "password" /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/ niczego nie znajduje, tak samo crontab -l dla root i zajrzenie do /var/spool/cron/crontabs/, a pliki opcji mają tryb 600. Przełącznik -s jest tu niezbędny, bo brakujący katalog przerwałby inaczej wywołanie z kodem wyjścia 2: na Debianie 13 /etc/cron.d po czystej instalacji bazy danych nie istnieje, nie ma tam bowiem jeszcze zainstalowanego żadnego pakietu cron.

Jeśli przerobisz te sześć punktów na nowym serwerze, wykluczysz już przeważającą większość ataków na bazy danych, nie instalując ani jednego dodatkowego programu. Reszta to dyscyplina w aktualizacjach i działający backup, którego odtworzenie zostało przećwiczone co najmniej raz.

Najczęstsze pytania

Czy w MariaDB w ogóle potrzebuję jeszcze hasła root?
Na Debianie 12 i 13 oraz Ubuntu 22.04 i 24.04 nie. Konto root@localhost korzysta tam domyślnie z wtyczki unix_socket, więc logowanie opiera się na tożsamości użytkownika systemowego. Dodatkowe hasło nie podnosi bezpieczeństwa, tylko tworzy kolejny sekret, który może zaginąć albo wyciec. Dopiero gdy jakieś narzędzie bezwzględnie wymaga logowania hasłem, załóż na to osobne konto, zamiast przestawiać root.
Dlaczego mój Debian twierdzi, że nie ma pakietu mysql-server?
Debian od kilku wydań nie dostarcza już własnego pakietu mysql-server. Ani na Debianie 12, ani na Debianie 13 nie ma dla niego instalowalnej wersji: apt-cache policy mysql-server wypisuje tam Candidate: (none) oraz pustą Version table, bo nazwa istnieje już tylko wirtualnie, przez mariadb-server. Pokazuje to apt-cache showpkg mysql-server. Polecenie instalacji brzmi apt install -y mariadb-server. Na Ubuntu 22.04 i 24.04 dostępne są obie bazy, MySQL w wersji 8.0.46, a MariaDB w 10.6 względnie 10.11.
Jak wrócić do bazy danych po utracie dostępu?
W MariaDB zatrzymaj usługę, ustaw poleceniem systemctl set-environment zmienną MYSQLD_OPTS na --skip-grant-tables --skip-networking i uruchom bazę ponownie. Po zalogowaniu potrzebne jest najpierw FLUSH PRIVILEGES, dopiero potem zresetujesz konto poleceniem ALTER USER. Na koniec usuń zmienną przez systemctl unset-environment. W MySQL 8.0 to nie zadziała, tam używa się pliku startowego przez --init-file, który ze względu na AppArmor musi leżeć w /var/lib/mysql-files.
Czym różni się ERROR 1698 od ERROR 1045?
ERROR 1698 (28000) oznacza, że konto oczekuje uwierzytelniania przez socket, a wywołujący użytkownik systemowy się nie zgadza. Tu zwykle pomaga sudo przed poleceniem. ERROR 1045 (28000) z dopiskiem (using password: YES) oznacza natomiast, że próbowano zalogować się hasłem, a hasło nie jest poprawne. Oba błędy mają więc całkiem inne przyczyny i całkiem inne rozwiązania.
Czy bind-address = 127.0.0.1 wystarczy do zabezpieczenia bazy danych?
To najważniejszy krok, ale nie jedyny. W MySQL 8.0 istnieje dodatkowo mysqlx-bind-address dla portu 33060, które trzeba ustawić osobno. Poza tym w /etc/mysql/mariadb.conf.d wygrywa plik przeczytany jako ostatni, więc własne zmiany zapisuj w pliku o wysokim numerze, na przykład 99-wlasne.cnf. Wynik sprawdzaj zawsze poleceniem ss -lntp, a nie w pliku konfiguracyjnym.
Dlaczego aplikacja nie powinna łączyć się z bazą jako root?
Bo wtedy każda luka w aplikacji prowadzi do pełnego dostępu do wszystkich baz danych, razem z tabelami uprawnień. Konto z GRANT SELECT, INSERT, UPDATE, DELETE ON mojabaza.* nie skasuje żadnej tabeli, nie przeczyta cudzej bazy i nie nada żadnych uprawnień. Zmiany schematu przeprowadzasz na osobnym koncie, używanym wyłącznie przy deployu.
Jak bezpiecznie przekazać hasło do bazy zadaniu cron?
Przez plik opcji z trybem 600, założony na przykład poleceniem install -m 600 /dev/null /root/.my.cnf, z sekcją [client] oraz wpisami user i password. Wywołanie wskazuje na niego przełącznikiem --defaults-extra-file, i to bezwzględnie jako pierwsza opcja, inaczej przełącznik zostanie zignorowany. Hasła podane bezpośrednio po -p w wierszu poleceń są widoczne dla każdego użytkownika przez ps, a MYSQL_PWD daje się podejrzeć podobnie, przez /proc.

MariaDB MySQL Baza danych Bezpieczeństwo serwera Debian Ubuntu Administracja Linuksem