Защита MariaDB и MySQL: что делать сразу после установки

Опубликовано 15 мин. чтения

После установки пакета база данных ещё не защищена. Это руководство проходит mysql_secure_installation по шагам, разбирает вопрос про unix_socket и показывает, как завести отдельного пользователя для каждого приложения.

Свежеустановленная база данных на root-сервере редко бывает настолько уязвимой, как утверждают старые руководства, и редко настолько защищённой, как обещает пакетный менеджер. Между этими двумя полюсами лежат ровно те действия, которые описывает эта статья: что на самом деле делает mysql_secure_installation, почему на современных системах вопрос про unix_socket решается иначе, чем в 2015 году, как выглядит учётная запись приложения с минимальными правами и как не дать паролям попасть в список процессов.

Все команды выполняются от имени root. Если вы работаете под обычным пользователем, добавляйте перед ними sudo. Приведённый вывод получен на Debian 13 и Debian 12, а также на Ubuntu 24.04 и Ubuntu 22.04.

Исходная точка: какой пакет работает в каком дистрибутиве

Первая ловушка поджидает ещё до первого шага по безопасности. Debian уже несколько лет не поставляет собственный пакет с именем mysql-server. Это имя существует там только как виртуальный пакет без собственной версии, и увидеть его можно лишь через зависимости mariadb-server:

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

То есть сравнение версий даёт результат только в Ubuntu, где это 8.0.46 и на 24.04, и на 22.04. В Debian вместо этого используют apt-cache showpkg mysql-server, который наглядно показывает виртуальную природу имени.

В Debian база данных, таким образом, всегда MariaDB. В Ubuntu доступны обе, и выбор придётся сделать самому. Версии в актуальных дистрибутивах:

Дистрибутивmariadb-servermysql-server
Debian 1311.8только виртуальное имя, версии нет
Debian 1210.11только виртуальное имя, версии нет
Ubuntu 24.0410.118.0.46
Ubuntu 22.0410.68.0.46

Установка выполняется как обычно:

apt update
apt install -y mariadb-server

Действительно ли установлена ожидаемая версия, показывает не одна лишь версия пакета, а работающий сервер:

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

Вторая ловушка: имя программы. Начиная с MariaDB 11.0 совместимые с MySQL имена лежат не в основном пакете, а в mariadb-client-compat и mariadb-server-compat. Поэтому на Debian 13 команда mysql --version либо ответит предупреждением, либо не найдётся вовсе:

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

Если скрипт должен работать на всех четырёх системах, в MariaDB стоит везде использовать mariadb, mariadb-dump и mariadb-secure-installation. Эти имена существуют начиная с MariaDB 10.5, то есть и на Ubuntu 22.04.

mysql_secure_installation по шагам

Инструмент называется по-разному в зависимости от сервера. В MariaDB каноническое имя: mariadb-secure-installation, в MySQL 8.0 остаётся mysql_secure_installation. Старое имя в MariaDB на Debian 12, а также на Ubuntu 24.04 и 22.04 по-прежнему есть в виде символьной ссылки, а на Debian 13 его уже нет: там существует только mariadb-secure-installation.

На список параметров стоит взглянуть, потому что программа принимает в том числе --defaults-file, --socket и --protocol. Правда, этот обзор есть только в руководстве:

man mariadb-secure-installation

Дело в том, что --help тут нет. Скрипт вообще не разбирает аргументы: он молча проглатывает неизвестный ключ и сразу запускает интерактивный диалог. Если при этом нет настоящей консоли (например, в конвейере или в контейнере без терминала), он бесконечно повторяет запрос пароля и сам никогда не завершается. Поэтому вызывать его нужно без аргументов и в интерактивной оболочке:

mariadb-secure-installation

На Debian 13 скрипт предваряет диалог недвусмысленным предупреждением:

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 считает такой прогон излишним и удалит скрипт в одной из будущих версий, о чём написано в /usr/share/doc/mariadb-server/README.Debian.gz. Пройти по шагам всё равно стоит: они показывают, что именно проверяется и почему на современной системе менять почти нечего.

Диалог начинается с вопроса о пароле для root. Формулировка зависит от версии скрипта:

Enter current password for root (enter for none):

Более новые версии спрашивают иначе:

Enter root user password or leave blank:

Смысл в обоих случаях один и тот же. На свежей установке пароля нет, поэтому здесь просто нажимают Enter.

Дальше идут собственно решения. Порядок и формулировки у MariaDB и MySQL различаются, но по сути речь об одних и тех же пяти пунктах.

Вопрос про unix_socket

В MariaDB следующим появляется такой вопрос:

Switch to unix_socket authentication [Y/n]

Он сбивает с толку, потому что на всех рассматриваемых здесь системах ответ на него уже дан. Начиная с MariaDB 10.4 учётная запись root@localhost по умолчанию защищена плагином unix_socket, и это верно как для 10.6 на Ubuntu 22.04, так и для 11.8 на Debian 13. В MySQL 8.0 аналог называется auth_socket, и мастер настройки прямо об этом сообщает:

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

На практике это означает следующее. Тот, кто вошёл в систему как пользователь root, попадает в базу командой mariadb без пароля. Тот, кто вошёл под другим пользователем, не попадёт туда вообще, даже зная правильный пароль. Это не недостаток, а более надёжный вариант. Нет пароля, который мог бы утечь из резервной копии, конфигурационного файла или скриншота. Поэтому ответ на вопрос звучит так: оставить «Да», а дополнительный пароль root на отдельно стоящем сервере приложений просто не нужен.

Проверить состояние можно только по настоящей таблице. Напрашивающийся запрос SELECT user, host, plugin FROM mysql.user здесь как раз вводит в заблуждение: для root он показывает значение mysql_native_password, хотя на самом деле работает unix_socket. В MariaDB начиная с 10.4 mysql.user — это лишь представление над mysql.global_priv, и такое представление знает про каждую учётную запись только один метод аутентификации. Кто на него полагается, ошибочно считает сервер настроенным на вход по паролю. Полное правило лежит в JSON-поле Priv настоящей таблицы:

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

Для root на localhost в MariaDB там записано примерно следующее:

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

Первая запись: парольный метод, проверяющий пароль против непригодного хеша строки invalid, попасть в который никто не может. Вторая запись, в auth_or: реально работающая аутентификация через сокет. Компактнее оба значения запрашиваются так:

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

Для root и localhost ожидается unix_socket: если не в столбце plugin, то в auth_or. Если там стоит исключительно парольный метод и в auth_or ничего нет, учётная запись работает только по паролю. В MySQL 8.0 таблицы mysql.global_priv нет, там mysql.user — настоящая таблица, и столбец plugin должен показывать auth_socket. Из представления MariaDB, кстати, по-прежнему можно читать, а вот писать в него уже нельзя.

Переключаться стоит только тогда, когда какому-то инструменту пароль нужен обязательно, например мониторингу, который работает не от root. Но и в этом случае разумнее завести вторую административную учётную запись, а не переделывать root. В MariaDB начиная с 11.6, то есть и в 11.8 на Debian 13, учётную запись можно дополнительно привязать к конкретному системному пользователю:

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

Так системный пользователь deploy может входить в базу как пользователь dbadmin, и совпадать имена при этом не обязаны. На Debian 12 и в версиях Ubuntu строка после AS пока игнорируется, там имя системного пользователя и имя пользователя базы должны совпадать.

Четыре оставшихся вопроса

Остальное сомнений не вызывает, и на все вопросы отвечают «Да»: удалить анонимных пользователей, запретить удалённый вход для root, удалить базу test вместе с правами на неё, перезагрузить таблицы привилегий. На актуальных Debian или Ubuntu анонимных пользователей и тестовой базы обычно и так нет, и скрипт просто сообщает, что делать было нечего.

В MySQL 8.0 добавляется вопрос, которого в MariaDB нет:

Would you like to setup VALIDATE PASSWORD component?

Этот компонент навязывает минимальные требования ко всем паролям, которые будут задаваться дальше. Он полезен, когда пользователей заводят несколько человек. И он мешает, когда скрипт подготовки генерирует случайные пароли, в которых по случайности не оказалось спецсимвола. Тогда скрипт прерывается с ошибкой:

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

Если компонент включают, генератор паролей стоит заранее настроить под него. Уровень по умолчанию: MEDIUM, он требует минимум восемь символов, буквы в верхнем и нижнем регистре, цифру и спецсимвол.

Если что-то пошло не так: как вернуться в базу

Чаще всего доступ теряют из-за благих намерений: переключились с unix_socket на пароль, а пароль потом потерялся. Или наоборот: старый скрипт заново создаёт /etc/mysql/debian.cnf, и внезапно ничего ни с чем не совпадает. Сообщения об ошибках, которые в такой момент ищут, выглядят так:

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

Ошибка 1698 означает, что учётная запись ожидает unix_socket, а вызывающий не является подходящим системным пользователем. Часто помогает уже одно sudo перед командой. Ошибка 1045 означает, что ожидается пароль и введённый пароль не подходит.

Если попасть внутрь не удаётся вообще, сервер запускают без проверки прав. В MariaDB это делается аккуратно, через переменную окружения, которую учитывает штатный systemd-юнит, и вообще без правки файлов из пакета:

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

Теперь подключаются командой mariadb -u root и возвращают состояние в норму. Важно сначала выполнить FLUSH PRIVILEGES, потому что без загруженных таблиц привилегий ALTER USER завершится ошибкой:

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

После этого обязательно уберите за собой, иначе сервер будет запускаться незащищённым после каждой перезагрузки:

systemctl stop mariadb
systemctl unset-environment MYSQLD_OPTS
systemctl start mariadb

В MySQL 8.0 на Ubuntu этот путь не работает: юнит такую переменную не учитывает, а у ALTER USER в режиме --skip-grant-tables есть дополнительные подводные камни. Там берут стартовый файл. Он должен лежать в каталоге, который AppArmor разрешает процессу сервера, иначе запуск закончится сообщением Can't open file. Каталог /var/lib/mysql-files разрешён, /tmp нет:

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

В редакторе задают переопределение команды запуска, причём пустая первая строка нужна для того, чтобы стереть исходное значение:

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

После systemctl daemon-reload и запуска учётная запись сброшена. Затем переопределение убирают командой systemctl revert mysql, а сам файл удаляют. Если базы данных работают на отдельных системах, дополнительные рекомендации по защите доступа есть в статье Защита SSH-сервера.

Отдельный пользователь для каждого приложения вместо root

Самый действенный шаг не встречается ни в одном мастере настройки. Приложения не должны подключаться от имени root, и GRANT ALL им тоже не нужен. Типичное веб-приложение читает и пишет строки, оно не создаёт базы данных и не читает файлы с сервера.

CREATE DATABASE shopdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'shopapp'@'localhost' IDENTIFIED BY 'ЗдесьДлинныйСлучайныйПароль';
GRANT SELECT, INSERT, UPDATE, DELETE ON shopdb.* TO 'shopapp'@'localhost';

Здесь важны три вещи. Первое: точка в shopdb.* вместо *.*. Права на *.* глобальны и распространяются в том числе на mysql и information_schema. Второе: запись @'localhost' вместо @'%'. Так учётная запись пригодна исключительно для локального использования, даже если порт когда-нибудь окажется открытым. Третье: отсутствует WITH GRANT OPTION, ведь учётная запись, способная раздавать права, фактически является администратором.

Изменения схемы выполняются тогда под второй учётной записью, которая используется только при развёртывании:

CREATE USER 'shopmigrate'@'localhost' IDENTIFIED BY 'ДругойДлинныйСлучайныйПароль';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, INDEX, REFERENCES ON shopdb.* TO 'shopmigrate'@'localhost';

Звучит как лишняя работа, но именно здесь SQL-инъекция в приложении превращается в досадную утечку данных вместо полной потери. Учётная запись без права DROP не может удалить таблицу.

Проверяют не формулировку GRANT, а результат:

SHOW GRANTS FOR 'shopapp'@'localhost';

Ожидаются ровно две строки: GRANT USAGE ON *.*, который означает лишь право на вход и никакого доступа к данным, и строка с четырьмя правами на shopdb.*. Если там появляется ALL PRIVILEGES ON *.*, значит точка стояла не в том месте. Вторая, более показательная проверка: вход под новой учётной записью и SHOW DATABASES;. Видны должны быть только information_schema и shopdb.

Частая ошибка при создании:

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

Почти всегда это означает, что учётная запись уже существует, часто как остаток от предыдущей попытки. Помогает DROP USER 'shopapp'@'localhost'; и повтор с самого начала.

bind-address: где база данных вообще слушает

Во всех четырёх дистрибутивах после установки пакета база данных слушает только на 127.0.0.1. У MariaDB эта строка находится в /etc/mysql/mariadb.conf.d/50-server.cnf, у MySQL 8.0 в /etc/mysql/mysql.conf.d/mysqld.cnf. Лучше посмотреть, чем предполагать:

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

В MySQL 8.0 есть вторая строка, которую охотно упускают из виду: mysqlx-bind-address управляет протоколом X на порту 33060. Изменив только bind-address, можно при некоторых условиях открыть доступ наполовину или оставить открытым второй порт.

Самая надёжная проверка: не конфигурационный файл, а ядро:

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

Если там стоит 0.0.0.0:3306 или *:3306, служба доступна из сети. Дополнительно сервер сообщает собственную точку зрения:

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

Здесь распространены две ловушки. Первая: файлы в mariadb.conf.d читаются в алфавитном порядке, и побеждает значение, прочитанное последним. Поэтому своё изменение, вынесенное в отдельный файл, называют 99-eigene.cnf, а не 10-eigene.cnf. Именно на этом спотыкается большинство сообщений о том, что MariaDB якобы игнорирует bind-address. Преимущество отдельного файла: обновления пакетов не спрашивают о разрешении конфликтов, потому что поставляемый файл остаётся нетронутым.

Вторая ловушка: если сетевой доступ не нужен вовсе, стоит пойти дальше bind-address и задать skip-networking. Тогда TCP-порта больше нет, остаётся только Unix-сокет. Для стандартного случая, когда веб-приложение и база данных живут на одном сервере, это правильная настройка, но она стоит нервов, если в конфигурации приложения указано 127.0.0.1 вместо localhost: при 127.0.0.1 клиентские библиотеки принудительно используют TCP.

Доступ извне только тогда, когда он действительно нужен

Порт базы данных в открытой сети находят в течение нескольких часов, после чего его перебирают непрерывно. Поэтому лучший ответ на вопрос о внешнем доступе: обойтись без него. Для нечастого обслуживания достаточно SSH-туннеля, который пробрасывает локальный порт на своей машине к сокету базы данных на удалённой стороне. Инструмент для работы с базой подключается тогда к 127.0.0.1, а сервер при этом ничего не открывает в сети.

Для постоянных соединений между несколькими серверами аккуратное решение: туннель WireGuard. База данных привязывается тогда исключительно к адресу туннеля, а не к публичному IP-адресу.

Если открытый порт всё же необходим, вместе работают четыре меры. Сервер привязывается ровно к одному внутреннему адресу. Файрвол пропускает только известный адрес источника. Учётная запись базы привязана к тому же адресу, то есть 'shopapp'@'10.0.0.5' и никогда 'shopapp'@'%'. И соединение принудительно шифруется:

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

При проверке снаружи встречаются две ошибки, которые часто путают. Первая означает, что никто не отвечает, то есть дело в файрволе или в bind-address:

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

Вторая означает, что сервер отвечает и осознанно отклоняет соединение, то есть для этого источника нет подходящей учётной записи:

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

А локально классика жанра в том, что служба попросту не запущена:

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

Пароли не в командной строке

Вызов mariadb -u shopapp -pGeheim123 работает и всё равно является ошибкой. Сервер говорит об этом сам:

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

За этим стоят две причины. Во-первых, строка попадает в историю оболочки. Во-вторых, командная строка процесса на обычной системе видна через ps любому вошедшему пользователю. На сервере с несколькими клиентами или несколькими службами это кража пароля без всяких усилий.

Правильный путь для человека: -p без значения следом. Тогда пароль спрашивают интерактивно, и в истории ничего не остаётся:

mariadb -u shopapp -p shopdb

Правильный путь для скриптов и cron-заданий: файл параметров с жёсткими правами. Создать его сразу с нужным режимом можно одной командой:

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

Содержимое:

[client]
user=backup
password=ЗдесьДлинныйСлучайныйПароль

После этого любой клиент находит учётные данные сам. Для разных задач заводят несколько файлов и ссылаются на нужный явно. При этом действует правило, на котором спотыкаются многие: --defaults-extra-file и --defaults-file должны быть первым параметром вызова, иначе они игнорируются без единого сообщения.

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

Переменная окружения MYSQL_PWD решением не является. Она видна в /proc, то есть примерно так же доступна, как и командная строка. В MySQL 8.0 дополнительно есть mysql_config_editor, который пишет файл ~/.mylogin.cnf. Его содержимое замаскировано, но не зашифровано, а MariaDB такого инструмента не знает. В смешанных средах простой файл параметров с режимом 600 остаётся более надёжным выбором.

Последний пункт касается резервного копирования. Дамп содержит всё, что видит приложение, и права на запись учётной записи для резервных копий при этом не нужны. Для mariadb-dump с --single-transaction обычно достаточно такого набора:

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

Приёмка: по каким признакам видно, что всё сделано

Команда, отработавшая без ошибок, не доказывает ничего. А вот эти шесть проверок доказывают:

  1. Учётная запись root использует аутентификацию через сокет: mariadb -e "SELECT User, Host, JSON_VALUE(Priv,'$.plugin') AS plugin, JSON_QUERY(Priv,'$.auth_or') AS auth_or FROM mysql.global_priv;" показывает для root значение unix_socket, либо в столбце plugin, либо в auth_or. Представление mysql.user для этого не годится, оно выводит только первый метод. В MySQL 8.0, наоборот, определяющим является столбец plugin в mysql.user, там должно стоять auth_socket.
  2. Анонимных учётных записей нет: mariadb -e "SELECT user, host FROM mysql.user WHERE user = '';" возвращает пустой результат.
  3. Тестовая база удалена: mariadb -e "SHOW DATABASES LIKE 'test';" не выводит ничего.
  4. Порт закрыт: ss -lntp для 3306 показывает либо вообще ничего, либо только 127.0.0.1.
  5. Учётная запись приложения ограничена: при входе под ней SHOW DATABASES; показывает только её собственную базу, а DROP TABLE завершается ошибкой.
  6. Нигде нет пароля открытым текстом: grep -rs "password" /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/ ничего не находит, как и crontab -l для root и просмотр каталога /var/spool/cron/crontabs/, а у файлов параметров стоит режим 600. Ключ -s здесь обязателен, потому что при отсутствующем каталоге вызов иначе прерывается с кодом возврата 2: на Debian 13 после установки одной только базы данных каталога /etc/cron.d нет, пакет cron там ещё не установлен.

Кто проходит эти шесть пунктов на новом сервере, тот уже исключает подавляющее большинство атак на базы данных, не устанавливая ни одной дополнительной программы. Остальное: дисциплина обновлений и работающая резервная копия, восстановление которой хотя бы раз проверили на практике.

Частые вопросы

Нужен ли в MariaDB вообще пароль для root?
На Debian 12 и 13, а также на Ubuntu 22.04 и 24.04 не нужен. Учётная запись root@localhost по умолчанию использует там плагин unix_socket, то есть вход выполняется по идентичности системного пользователя. Дополнительный пароль безопасности не добавляет, а лишь создаёт ещё один секрет, который можно потерять или который может утечь. И только если какому-то инструменту обязательно нужен вход по паролю, для него заводят отдельную учётную запись, а не переделывают root.
Почему Debian сообщает, что пакета mysql-server не существует?
Debian уже несколько выпусков подряд не поставляет собственный пакет mysql-server. Ни на Debian 12, ни на Debian 13 устанавливаемой версии для него нет: apt-cache policy mysql-server показывает там Candidate: (none) и пустую Version table, потому что имя существует только виртуально, через mariadb-server. Наглядно это показывает apt-cache showpkg mysql-server. Команда для установки: apt install -y mariadb-server. На Ubuntu 22.04 и 24.04 доступны оба варианта, MySQL версии 8.0.46 и MariaDB версии 10.6 либо 10.11 соответственно.
Как вернуться в базу данных, если доступ к ней потерян?
В MariaDB службу останавливают, командой systemctl set-environment задают переменной MYSQLD_OPTS значение --skip-grant-tables --skip-networking и запускают её заново. После входа сначала требуется FLUSH PRIVILEGES, и только потом учётную запись можно сбросить через ALTER USER. Затем переменную снова убирают командой systemctl unset-environment. В MySQL 8.0 это не работает, там используют стартовый файл через --init-file, который из-за AppArmor должен лежать в /var/lib/mysql-files.
Чем ERROR 1698 отличается от ERROR 1045?
ERROR 1698 (28000) означает, что учётная запись ожидает аутентификацию через сокет, а вызывающий системный пользователь для неё не подходит. Здесь обычно помогает sudo перед командой. ERROR 1045 (28000) с пометкой (using password: YES), наоборот, означает, что была попытка входа по паролю и пароль не совпал. То есть у этих двух ошибок совершенно разные причины и разные решения.
Достаточно ли bind-address = 127.0.0.1 для защиты базы данных?
Это важнейший шаг, но не единственный. В MySQL 8.0 дополнительно есть mysqlx-bind-address для порта 33060, который задаётся отдельно. Кроме того, в /etc/mysql/mariadb.conf.d побеждает файл, прочитанный последним, поэтому собственные изменения помещают в файл с высоким номером, например 99-eigene.cnf. Результат всегда проверяют командой ss -lntp, а не по конфигурационному файлу.
Почему приложению не следует обращаться к базе данных от имени root?
Потому что тогда любая уязвимость в приложении открывает полный доступ ко всем базам данных, включая таблицы привилегий. Учётная запись с правами GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* не может ни удалить таблицу, ни прочитать чужую базу, ни выдать права. Изменения схемы выполняются под отдельной учётной записью, которая используется только при развёртывании.
Как безопасно передать cron-заданию пароль от базы данных?
Через файл параметров с режимом 600, созданный, например, командой install -m 600 /dev/null /root/.my.cnf, с разделом [client] и полями user и password. Вызов ссылается на него ключом --defaults-extra-file, причём обязательно первым параметром, иначе ключ игнорируется. Пароли, указанные прямо после -p в командной строке, видны любому пользователю через ps, а MYSQL_PWD точно так же просматривается через /proc.

MariaDB MySQL База данных Безопасность сервера Debian Ubuntu Администрирование Linux