RE:NODE

MySQL 8.4 и mysql_native_password: ошибки и решение

Автор Команда RE:NODE
15 мин чтения


Если приложение перестало подключаться после перехода на MySQL 8.4, а сервер отвечает Plugin 'mysql_native_password' is not loaded, значит, учётная запись всё ещё использует старый способ входа на основе SHA-1, а в 8.4 он по умолчанию выключен. Выхода два. Настоящее решение - перевести учётную запись на caching_sha2_password командой ALTER USER ... IDENTIFIED WITH caching_sha2_password BY '...', предварительно убедившись, что драйвер приложения его поддерживает. Временная мера - запустить сервер с mysql_native_password=ON: это работает в 8.4 и больше нигде, потому что в MySQL 9.0 плагин удалили полностью. Похожие ошибки с другой стороны - старый клиент, который не умеет caching_sha2_password, или новый клиент 9.x, который больше не умеет mysql_native_password - лечатся иначе, и умение их различать составляет большую часть работы.

В этой статье подробно разобрана именно аутентификация при переходе на 8.4. Более широкий список изменений между 8.0 и 8.4 есть в статье MySQL 8.4 LTS: что изменилось, а создание учётных записей и выдача привилегий описаны в статье Пользователи и привилегии MySQL.

Что менялось от версии к версии#

Плагин уходил годами. Между версиями отличается лишь то, насколько громко MySQL об этом сообщает.

Версияmysql_native_passwordПо умолчанию для новых учётных записей
5.7Доступен, используется по умолчаниюmysql_native_password
8.0Доступен, объявлен устаревшим (предупреждения с 8.0.34)caching_sha2_password
8.4 LTSЕсть, но отключён, если не включить при запускеcaching_sha2_password
9.0 и новееУдалён из сервераcaching_sha2_password

Из-за лет жизни на 8.0 у многих установок до сих пор остаются учётные записи со старым плагином. Значение по умолчанию сменилось в 8.0.4, но сервер, обновлённый с 5.7, оставил все существующие учётные записи на старом плагине, а многие инструкции по установке, файлы Docker Compose и панели хостинга прописывали default_authentication_plugin=mysql_native_password, чтобы избежать проблем с драйверами. Эти учётные записи продолжали работать на протяжении всей серии 8.0. В 8.4 этому приходит конец.

Обратите внимание на тонкий момент в строке про 8.4. Плагин не исчез: код всё ещё в сервере, учётные записи с ним по-прежнему есть в mysql.user, а данные при обновлении остаются нетронутыми. Он просто не загружен, поэтому вход под такой учётной записью завершиться не может, как и CREATE USER или ALTER USER, где он указан. На 9.x после обновления те же учётные записи тоже остаются, но переключателя, чтобы вернуть их к жизни, уже нет.

Чем различаются два плагина#

mysql_native_password хранит двойной хеш SHA-1 пароля без соли. У двух учётных записей с одинаковым паролем хеши совпадают, а SHA-1 достаточно быстр, чтобы утёкшую таблицу mysql.user можно было дёшево перебрать. Сам вход - это обмен «вызов-ответ», при котором пароль не передаётся, поэтому два десятилетия он работал и без TLS.

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

  • Быстрая аутентификация - учётная запись есть в кеше, клиент отвечает на случайную строку ответом на основе SHA-256, и его пускают без передачи пароля по сети. Это обычный случай.
  • Полная аутентификация - учётной записи в кеше нет: так бывает при первом входе после запуска сервера, после FLUSH PRIVILEGES и после смены пароля учётной записи. Серверу нужен сам пароль, чтобы сверить его с сохранённым хешем, поэтому клиент обязан его отправить, а MySQL примет его только по защищённому каналу: через TLS-соединение, Unix-сокет или в зашифрованном виде открытым RSA-ключом сервера.
есть в кеше?данетКлиентдрайвер или mysql CLIКеш серверав памятиБыстрая аутент.только scrambleПолная аутент.нужен парольTLS, сокет, RSAиначе отказ
caching_sha2_password выбирает путь при каждом входе

Этот второй путь и есть источник жалоб «то работало, а теперь нет». Приложение подключается без TLS, несколько дней всё хорошо, потому что учётная запись в кеше, сервер перезапускается, и следующий вход не проходит, пока кто-нибудь не войдёт один раз по TLS и не прогреет кеш. Если с самого начала настроить клиент на TLS или на получение RSA-ключа, проблема не появится вовсе.

Ошибки и с какой стороны они приходят#

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

ОшибкаОткудаЗначение
ERROR 1524 (HY000): Plugin 'mysql_native_password' is not loadedСерверУчётная запись использует нативный плагин, а сервер его отключил или удалил
ERROR 2059 (HY000): Authentication plugin 'caching_sha2_password' cannot be loadedКлиентКлиентская библиотека слишком старая для нового плагина
ERROR 2059 (HY000): Authentication plugin 'mysql_native_password' cannot be loadedКлиентКлиент 9.x подключается к учётной записи или серверу, которые всё ещё используют старый плагин
ERROR 2061 (HY000): ... Authentication requires secure connectionКлиентПолная аутентификация по обычному соединению без RSA-ключа

Plugin 'mysql_native_password' is not loaded

Так сервер 8.4 или 9.x говорит об учётной записи, а не о клиенте. Любой клиент, каким бы новым он ни был, получит эту ошибку при входе под учётной записью, у которой в столбце plugin всё ещё указано mysql_native_password. Она же возникает как ошибка выражения при выполнении CREATE USER ... IDENTIFIED WITH mysql_native_password - эта строка встречается во множестве старых руководств по установке и скриптов развёртывания. Исправьте учётную запись или, на 8.4, включите плагин; на 9.x возможен только первый вариант.

Authentication plugin 'caching_sha2_password' cannot be loaded

Полное сообщение обычно заканчивается путём, примерно /usr/lib/mysql/plugin/caching_sha2_password.so: cannot open shared object file. Клиент собран с библиотекой времён MySQL 5.7 (или со старой клиентской библиотекой MariaDB), которая ничего не знает о caching_sha2_password, поэтому ищет файл плагина на диске и не находит. Создание такого файла не поможет. Обновите клиент или драйвер; возвращать учётную запись на нативный плагин - это неверное направление, ведь 9.x так вообще не умеет.

Authentication plugin 'mysql_native_password' cannot be loaded

Это зеркальный случай, и он стал частым с тех пор, как клиенты 9.x появились в менеджерах пакетов - в частности, в формуле mysql у Homebrew. Клиент 9.x больше не поставляется с нативным плагином, поэтому при подключении к старому серверу 5.7, к серверу MariaDB или к учётной записи 8.x на старом плагине сбой происходит на стороне клиента. Решение - перевести учётную запись на caching_sha2_password, если сервер это поддерживает, либо для остальных серверов использовать клиент 8.4 LTS (brew install mysql-client@8.4 на Mac).

Как это формулируют драйверы

Драйверы, которые сами реализуют протокол, описывают те же сбои по-своему:

ДрайверСообщение
PHP mysqli / PDO, старый PHPThe server requested authentication method unknown to the client [caching_sha2_password]
Пакет mysql для Node.jsER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server
Java Connector/JPublic Key Retrieval is not allowed

Java-ошибка не связана с версией: Connector/J плагин понимает, но по умолчанию отказывается получать RSA-ключ по незашифрованному соединению. Это та же ситуация полной аутентификации, что и ERROR 2061, о ней ниже.

Найдите учётные записи, которые ещё на старом плагине#

Прежде чем что-то менять, составьте их список. Выполните запрос от имени root или другой учётной записи, которая может читать схему mysql:

sql
SELECT user, host, plugin, password_last_changedFROM mysql.userWHERE plugin = 'mysql_native_password'ORDER BY user, host;

Ожидайте увидеть учётные записи приложений, созданные старыми скриптами, учётные записи, перенесённые с сервера 5.7 через дамп (вывод SHOW CREATE USER несёт плагин и хеш с собой), а иногда и сам root. Внутренние учётные записи, такие как mysql.sys, mysql.session и mysql.infoschema, на современном сервере используют caching_sha2_password и заблокированы; не трогайте их.

Помните, что учётная запись - это пользователь и хост вместе, поэтому 'app'@'%' и 'app'@'localhost' - это разные строки, и плагины у них могут отличаться. Приложение, которое подключается из двух мест, может столкнуться с проблемой только из одного из них. Запрос SELECT CURRENT_USER(); после входа покажет, какой строке фактически соответствует сессия.

Если вы проверяете сервер 8.0 перед обновлением, то то же самое сообщает и проверка обновления в MySQL Shell в рамках более широкого сканирования:

bash
$ mysqlsh root@db.example.net:3306 -- util check-for-server-upgrade --target-version=8.4.6

Проверьте клиент, прежде чем переносить учётную запись#

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

Клиент или драйверПоддержка caching_sha2_password
PHP mysqli и PDO (mysqlnd)Да, начиная с PHP 7.4
Node.js mysql2Да
Node.js mysql (mysqljs)Нет - перейдите на mysql2
Python PyMySQL, mysql-connector-pythonДа, в текущих релизах
Java Connector/J8.0 и новее
.NET MySqlConnector, MySql.DataДа; MySql.Data - начиная с 8.0
Go go-sql-driver/mysql1.4 и новее
mysql CLI, mysqldump8.0 и новее

Несколько практических замечаний к строкам выше.

PHP. Поддержку добавили в сериях 7.1 и 7.2, но такой старый PHP давно снят с поддержки безопасности, и его всё равно нужно обновить. На PHP 7.4 и во всех релизах 8.x mysqli и pdo_mysql используют mysqlnd и работают с обоими плагинами. Сайт на WordPress или Laravel, который падает с сообщением «unknown to the client», почти всегда запускается на старом бинарнике PHP там, где вы этого не ждёте - в контейнере для cron, в CLI на старом образе, - тогда как PHP веб-сервера в порядке. Параметры подключения описаны в статье PHP PDO и MySQL.

Node.js. Исходный пакет mysql никогда не поддерживал caching_sha2_password и фактически не развивается. У mysql2 почти такой же API для большинства кода, он поддерживает оба плагина и промисы; в большинстве проектов вся замена сводится к названию пакета и строке импорта. ORM вроде Sequelize, Knex и TypeORM в текущих версиях уже используют для MySQL именно mysql2.

Python. mysqlclient (модуль MySQLdb) - это обёртка над клиентской библиотекой на C, с которой он собран, поэтому поддержка зависит от этой библиотеки, а не от версии самого модуля. Сборка (wheel) с клиентской библиотекой MySQL 8 или новее работает; с библиотекой старой - нет.

Настольные программы. MySQL Workbench 8.x, DBeaver и HeidiSQL в текущих версиях поддерживают новый плагин; проблемы возникают с копиями, установленными много лет назад и ни разу не обновлявшимися. Настройки подключения для каждой из них есть в статье GUI-клиенты MySQL.

Перенос учётной записи через ALTER USER#

Выражение, которое переносит учётную запись, короткое, и ему нужен пароль:

sql
ALTER USER 'app'@'%'  IDENTIFIED WITH caching_sha2_password BY 'the-current-or-a-new-password';

Сохранённые хеши двух плагинов - это разные функции от пароля, и хеш SHA-1 нельзя преобразовать в хеш SHA-256. В MySQL нельзя сменить плагин и сохранить старый хеш, поэтому вы задаёте пароль: тот же, что приложение уже использует, если вы его знаете и не хотите менять конфигурацию, или новый. Если текущий пароль никто не знает, это возможность сгенерировать нормальный пароль и положить его туда, откуда его читает приложение.

Этому выражению не нужно, чтобы нативный плагин был включён. На стандартном сервере 8.4 или на 9.x после обновления root может перенести нативную учётную запись напрямую; войти под ней до этого нельзя, но изменить её можно.

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

sql
SELECT CONCAT('ALTER USER ', QUOTE(user), '@', QUOTE(host),  ' IDENTIFIED WITH caching_sha2_password BY ''CHANGE_ME'';') AS stmtFROM mysql.userWHERE plugin = 'mysql_native_password';

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

sql
SHOW CREATE USER 'app'@'%';
bash
$ mysql -h db.example.net -P 3306 -u app -p --ssl-mode=REQUIRED -e "SELECT CURRENT_USER();"

Перенос без простоя

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

  1. Вторая учётная запись. Создайте 'app2'@'%' с caching_sha2_password и теми же правами (SHOW GRANTS FOR 'app'@'%' выдаст выражения для копирования), переключите конфигурацию приложения на неё, выкатите, убедитесь, что под старой учётной записью никто не подключается, затем выполните DROP USER 'app'@'%'.
  2. Та же учётная запись, тот же пароль, быстрый деплой. Если пароль оставить прежним, изменится только плагин. Клиенты, которые уже поддерживают caching_sha2_password, переподключатся без каких-либо изменений в конфигурации, так что перенос будет незаметен - при условии, что драйвер обновили заранее.

Функция двух паролей (RETAIN CURRENT PASSWORD) здесь не поможет: MySQL не разрешает её в ALTER USER, который меняет плагин аутентификации.

TLS, RSA-ключи и сбой при первом входе#

Когда учётные записи переведены на caching_sha2_password, остальные сбои связаны с тем, что для полной аутентификации нужен защищённый канал. Чистое решение - TLS: MySQL 8.x при первом запуске создаёт самоподписанный сертификат, а клиентские библиотеки по умолчанию пробуют TLS (--ssl-mode=PREFERRED). Клиент с отключённым TLS или библиотека, которая по умолчанию TLS не использует, переходят на RSA, а некоторые отказываются получать ключ, пока им этого явно не разрешат.

КлиентПараметр, разрешающий полную аутентификацию без TLS
mysql CLI--get-server-public-key
Connector/JallowPublicKeyRetrieval=true
MySqlConnector (.NET)AllowPublicKeyRetrieval=true

Получение открытого ключа по незашифрованному соединению открывает возможность атаки «человек посередине», когда злоумышленник подставляет свой ключ, поэтому по умолчанию оно выключено. В частной сети это разумный компромисс; через интернет используйте TLS и оставьте получение ключа выключенным. Правильно настроенный JDBC URL выглядит так:

code
jdbc:mysql://db.example.net:3306/appdb?sslMode=REQUIRED

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

Как снова включить mysql_native_password в 8.4#

Когда приложение нельзя исправить сегодня - например, это сторонний бинарник со старой клиентской библиотекой внутри, - в 8.4 плагин можно загрузить снова. Это параметр запуска сервера:

my.cnf
[mysqld]mysql_native_password=ON

В виде параметра командной строки это --mysql-native-password=ON, именно так его записывают в строке command: в Docker Compose. Перезапустите сервер и проверьте, что параметр применился:

sql
SELECT plugin_name, plugin_statusFROM information_schema.pluginsWHERE plugin_name = 'mysql_native_password';

Статус ACTIVE означает, что учётные записи на старом плагине снова могут входить. Строка с другим статусом или её отсутствие означает, что параметр не прочитан - проверьте, какие файлы конфигурации сервер действительно загружает, командой mysqld --verbose --help | grep -A1 "Default options".

Здесь есть две ловушки. Первая - настройка времён 8.0 default_authentication_plugin=mysql_native_password, которая есть во многих старых файлах Compose и фрагментах my.cnf. Эту переменную удалили в 8.4, и сервер отказывается запускаться с ней в конфигурации, сообщая в журнале ошибок о неизвестной переменной. Удалите её; если вам действительно нужен нативный плагин по умолчанию для новых учётных записей, в 8.4 для этого есть authentication_policy, но создавать новые нативные учётные записи на сервере, где у плагина нет будущего, почти незачем. Вторая ловушка - ожидать, что этот параметр работает на 9.x. Его там нет, а неизвестный параметр так же мешает запуску.

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

Обновление с 8.4 до 9.x#

В релизах MySQL 9.x Innovation и в следующем LTS нативного плагина нет вовсе. Обновление с оставшимися нативными учётными записями проходит успешно - сервер запускается, данные целы, - но эти записи непригодны, пока кто-нибудь их не изменит, и ошибка будет та же: Plugin 'mysql_native_password' is not loaded. Если среди них root, этому «кому-нибудь» придётся войти другим способом, то есть через перезапуск с --skip-grant-tables, которого вы хотите избежать.

Поэтому порядок жёсткий: обновите драйверы, перенесите учётные записи на 8.4, убедитесь, что запрос из предыдущего раздела не возвращает строк, и только потом думайте о 9.x. Для рабочей базы данных запускать Innovation-релизы почти незачем; 8.4 будет поддерживаться ещё годами. Суть в том, что перенос - это одна и та же работа, делаете ли вы её сейчас или потом, под давлением.

При переносе базы между серверами логическим дампом то же относится и к учётным записям, которые вы переносите. Выражение SHOW CREATE USER, скопированное со старого сервера, содержит IDENTIFIED WITH 'mysql_native_password' AS '*...', и при повторном выполнении на 8.4 оно завершится ошибкой или воссоздаст проблему. Вместо этого создайте учётные записи на новом сервере с caching_sha2_password - как провести такой перенос, рассказывает статья Перенос MySQL на новый хост. Тарифы MySQL RE:NODE работают на 8.4 LTS, а root, база данных приложения и пользователь приложения создаются на текущем плагине по умолчанию, так что учётным записям, с которых вы начинаете, перенос не нужен; тем, что вы приносите со старого сервера, он может понадобиться.

FAQ#

Медленнее ли caching_sha2_password, чем mysql_native_password?

Только при полной аутентификации, и заметно лишь для нагрузок, которые открывают новое соединение на каждый запрос при пустом кеше. После первого входа учётная запись попадает в кеш, и быстрая аутентификация стоит примерно столько же, сколько старый плагин. Пул соединений, который и так используется в большинстве приложений, сводит разницу на нет; как подобрать его размер, объясняет статья Лимиты соединений и пулы в MySQL.

Можно ли сменить плагин пользователя, не зная пароля?

Нет. Плагин и хеш неразделимы, а MySQL не умеет получить хеш SHA-256 из сохранённого хеша SHA-1. Пароль нужно задать в том же выражении ALTER USER. Если старый пароль потерян, задайте новый и обновите конфигурацию приложения.

Почему вход работает с моего ноутбука, но не с сервера?

Обычно потому, что на двух машинах разные клиентские библиотеки. На ноутбуке современный mysql CLI или GUI-программа, а приложение на сервере использует старый драйвер, зашитый в образ или в lock-файл. Сравните точный текст ошибки с таблицей выше - он подскажет, отказывает сервер или клиент.

Есть ли такое же изменение в MariaDB?

Нет, и в этом вопросе их нельзя считать взаимозаменяемыми. MariaDB по-прежнему использует mysql_native_password и собственные плагины и не реализует на сервере caching_sha2_password из MySQL. Поэтому клиент MySQL 9.x не может войти на многие серверы MariaDB: он лишился единственного общего с ними плагина.

Нужен ли FLUSH PRIVILEGES после ALTER USER?

Нет. ALTER USER сразу обновляет кеши прав и аутентификации в памяти. Если всё же выполнить FLUSH PRIVILEGES, это ещё и очистит кеш caching_sha2_password, и каждой учётной записи при следующем входе придётся пройти полную аутентификацию - с TLS это безвредно, а для клиентов без него становится источником неожиданных сбоев.

Это было полезно?