mysql> select * from mysql.user where User="root";
Empty set (0.00 sec)
そんなアホな!rootユーザを消してしまった場合の復活の手順を記す。概要はこう。
- MySQLを停止し、セーフモードで起動
- rootユーザを作成
- 権限を付与
- セーフモードのMySQLをkill
- 通常モードのMySQLでパスワードを設定
MySQLのバージョンは8.0、LinuxのディストリビューションはUbuntu 20.04。
# mysql --version
mysql Ver 8.0.26-0ubuntu0.20.04.2 for Linux on x86_64 ((Ubuntu))
# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.3 LTS"
MySQLの停止、セーフモードで起動
権限を無視してMySQLを操作する必要があるため、オプションをつけてセーフモードでMySQLを起動する必要がある。これに先立ちMySQLを一旦停止する必要がある。
systemctlコマンドでMySQLを停止する。
# systemctl stop mysql
続いて、MySQLを権限テーブルを無視するオプションをつけて、セーフモードで起動する。
# mysqld_safe --skip-grant-tables
[1] 126431
root@i-12100000190719:/var/www/killtime# 2021-10-04T03:27:58.764196Z mysqld_safe Logging to '/var/log/mysql/error.log'.
2021-10-04T03:27:58.766445Z mysqld_safe Directory '/var/run/mysqld' for UNIX socket file don't exists.
……ソケットファイルに使う/var/run/mysqld/が存在しないと怒られる。 Ctrl+Cで中断して、ディレクトリを作成する。
# mkdir /var/run/mysqld
# chown mysql:mysql /var/run/mysqld
# mysqld_safe --skip-grant-tables
[1] 142867
root@host:/path/to/current/dir# 2021-10-07T03:29:22.417651Z mysqld_safe Logging to '/var/log/mysql/error.log'.
2021-10-07T03:29:22.445744Z mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql
なんかメッセージが出て動かせない状態になる。
セーフモードで起動するコマンド”mysqld_safe –skip-grant-tables”について、末尾に「&」をつけてバックグラウンド起動するように書いてある記事があるが、mysql_safeがmysqldをdaemonとして起動させるからか、私の環境ではフォアグラウンドでもバックグラウンドでも同じだった。
Ctrl+CないしCtrl+Zで↑を閉じ、プロセスを確認するとmysqlが起動していることがわかる。
# ps aux | grep mysql
root 126842 0.0 0.4 10956 4840 pts/1 S 12:36 0:00 sudo mysqld_safe --skip-grant-tables
root 126843 0.0 0.1 2608 1784 pts/1 S 12:36 0:00 /bin/sh /usr/bin/mysqld_safe --skip-grant-tables
mysql 127000 0.8 38.0 1285732 382696 pts/1 Sl 12:36 0:01 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib/mysql/plugin --user=mysql --skip-grant-tables --log-error=/var/log/mysql/error.log --pid-file=i-12100000190719.pid
MySQLにログインする。パスワード不要。
# mysql
mysql>
rootユーザの作成
データベースを変更。
mysql> use mysql;
Databases changed
ここでUserテーブルのカラムとかデフォルト値とか見てみる。
mysql> show columns in user;
+--------------------------+-----------------------------------+------+-----+-----------------------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------------------+-----------------------------------+------+-----+-----------------------+-------+
| Host | char(255) | NO | PRI | | |
| User | char(32) | NO | PRI | | |
| Select_priv | enum('N','Y') | NO | | N | |
| Insert_priv | enum('N','Y') | NO | | N | |
| Update_priv | enum('N','Y') | NO | | N | |
| Delete_priv | enum('N','Y') | NO | | N | |
| Create_priv | enum('N','Y') | NO | | N | |
| Drop_priv | enum('N','Y') | NO | | N | |
| Reload_priv | enum('N','Y') | NO | | N | |
| Shutdown_priv | enum('N','Y') | NO | | N | |
| Process_priv | enum('N','Y') | NO | | N | |
| File_priv | enum('N','Y') | NO | | N | |
| Grant_priv | enum('N','Y') | NO | | N | |
| References_priv | enum('N','Y') | NO | | N | |
| Index_priv | enum('N','Y') | NO | | N | |
| Alter_priv | enum('N','Y') | NO | | N | |
| Show_db_priv | enum('N','Y') | NO | | N | |
| Super_priv | enum('N','Y') | NO | | N | |
| Create_tmp_table_priv | enum('N','Y') | NO | | N | |
| Lock_tables_priv | enum('N','Y') | NO | | N | |
| Execute_priv | enum('N','Y') | NO | | N | |
| Repl_slave_priv | enum('N','Y') | NO | | N | |
| Repl_client_priv | enum('N','Y') | NO | | N | |
| Create_view_priv | enum('N','Y') | NO | | N | |
| Show_view_priv | enum('N','Y') | NO | | N | |
| Create_routine_priv | enum('N','Y') | NO | | N | |
| Alter_routine_priv | enum('N','Y') | NO | | N | |
| Create_user_priv | enum('N','Y') | NO | | N | |
| Event_priv | enum('N','Y') | NO | | N | |
| Trigger_priv | enum('N','Y') | NO | | N | |
| Create_tablespace_priv | enum('N','Y') | NO | | N | |
| ssl_type | enum('','ANY','X509','SPECIFIED') | NO | | | |
| ssl_cipher | blob | NO | | NULL | |
| x509_issuer | blob | NO | | NULL | |
| x509_subject | blob | NO | | NULL | |
| max_questions | int unsigned | NO | | 0 | |
| max_updates | int unsigned | NO | | 0 | |
| max_connections | int unsigned | NO | | 0 | |
| max_user_connections | int unsigned | NO | | 0 | |
| plugin | char(64) | NO | | caching_sha2_password | |
| authentication_string | text | YES | | NULL | |
| password_expired | enum('N','Y') | NO | | N | |
| password_last_changed | timestamp | YES | | NULL | |
| password_lifetime | smallint unsigned | YES | | NULL | |
| account_locked | enum('N','Y') | NO | | N | |
| Create_role_priv | enum('N','Y') | NO | | N | |
| Drop_role_priv | enum('N','Y') | NO | | N | |
| Password_reuse_history | smallint unsigned | YES | | NULL | |
| Password_reuse_time | smallint unsigned | YES | | NULL | |
| Password_require_current | enum('N','Y') | YES | | NULL | |
| User_attributes | json | YES | | NULL | |
+--------------------------+-----------------------------------+------+-----+-----------------------+-------+
↑のうち、Host, Userは主キー。ssl_cipher, x509_issuer,x509_subjectはNull値禁止かつデフォルト値Nullなので、この5つのカラムは何か埋めてrootユーザのレコードをインサートする。
rootユーザを作成:
mysql> insert into user (user , host, ssl_cipher, x509_issuer, x509_subject) values('root','localhost', '','','');
Query OK, 1 row affected (0.01 sec)
権限を付与
ここで各カラムの値を見る。
mysql> select * from mysql.user where User='root' \G
*************************** 1. row ***************************
Host: localhost
User: root
Select_priv: N
Insert_priv: N
Update_priv: N
Delete_priv: N
Create_priv: N
Drop_priv: N
Reload_priv: N
Shutdown_priv: N
Process_priv: N
File_priv: N
Grant_priv: N
References_priv: N
Index_priv: N
Alter_priv: N
Show_db_priv: N
Super_priv: N
Create_tmp_table_priv: N
Lock_tables_priv: N
Execute_priv: N
Repl_slave_priv: N
Repl_client_priv: N
Create_view_priv: N
Show_view_priv: N
Create_routine_priv: N
Alter_routine_priv: N
Create_user_priv: N
Event_priv: N
Trigger_priv: N
Create_tablespace_priv: N
ssl_type:
ssl_cipher: NULL
x509_issuer: NULL
x509_subject: NULL
max_questions: 0
max_updates: 0
max_connections: 0
max_user_connections: 0
plugin: caching_sha2_password
authentication_string: NULL
password_expired: N
password_last_changed: NULL
password_lifetime: NULL
account_locked: N
Create_role_priv: N
Drop_role_priv: N
Password_reuse_history: NULL
Password_reuse_time: NULL
Password_require_current: NULL
User_attributes: NULL
1 row in set (0.00 sec)
このうち、カラム名が *_priv となっているのが特権に関する情報。今デフォルト値ですべてNになっているので、Yで更新すればいい。rootユーザなんで全部Y。
以下のようなクエリを作る。手でタイプするにはダルく、プログラミングするほどでもない量だ。スプレッドシートなんかでうまいことやるといいかもしれない。私はそうした。
update user set Select_priv = 'Y' where user = 'root';
update user set Insert_priv = 'Y' where user = 'root';
update user set Update_priv = 'Y' where user = 'root';
update user set Delete_priv = 'Y' where user = 'root';
update user set Create_priv = 'Y' where user = 'root';
update user set Drop_priv = 'Y' where user = 'root';
update user set Reload_priv = 'Y' where user = 'root';
update user set Shutdown_priv = 'Y' where user = 'root';
update user set Process_priv = 'Y' where user = 'root';
update user set File_priv = 'Y' where user = 'root';
update user set Grant_priv = 'Y' where user = 'root';
update user set References_priv = 'Y' where user = 'root';
update user set Index_priv = 'Y' where user = 'root';
update user set Alter_priv = 'Y' where user = 'root';
update user set Show_db_priv = 'Y' where user = 'root';
update user set Super_priv = 'Y' where user = 'root';
update user set Create_tmp_table_priv = 'Y' where user = 'root';
update user set Lock_tables_priv = 'Y' where user = 'root';
update user set Execute_priv = 'Y' where user = 'root';
update user set Repl_slave_priv = 'Y' where user = 'root';
update user set Repl_client_priv = 'Y' where user = 'root';
update user set Create_view_priv = 'Y' where user = 'root';
update user set Show_view_priv = 'Y' where user = 'root';
update user set Create_routine_priv = 'Y' where user = 'root';
update user set Alter_routine_priv = 'Y' where user = 'root';
update user set Create_user_priv = 'Y' where user = 'root';
update user set Event_priv = 'Y' where user = 'root';
update user set Trigger_priv = 'Y' where user = 'root';
update user set Create_tablespace_priv = 'Y' where user = 'root';
update user set Create_role_priv = 'Y' where user = 'root';
update user set Drop_role_priv = 'Y' where user = 'root';
そして、権限テーブルの設定を反映させておく。
mysql> flush privileges;
さらにrootユーザのパスワードを設定すれば完了だが、権限テーブルをスキップした状態ではクエリを受け付けてくれない。
mysql> alter user root identified by '{PASSWORD}';
ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement
なので、一旦ログアウトし、セーフモードではなく普通のモードでログインする。
セーフモードのMySQLをkill
セーフモードで起動しているmysqlのプロセスIDを確認し、killする。
# ps aux | grep mysql
root 136115 0.0 0.3 10960 3360 pts/1 T 10月06 0:00 sudo mysqld_safe --skip-grant-tables
root 136116 0.0 0.1 2608 1892 pts/1 T 10月06 0:00 /bin/sh /usr/bin/mysqld_safe --skip-grant-tables
mysql 136277 0.2 38.3 1299052 384896 pts/1 Sl 10月06 3:57 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib/mysql/plugin --user=mysql --skip-grant-tables --log-error=/var/log/mysql/error.log --pid-file=i-12100000190719.pid
root 142278 0.0 0.0 7476 676 pts/1 S+ 12:08 0:00 grep --color=auto mysql
↑で言うと、上から3つをすべてkillすれば消えた。
# kill -9 136115
通常モードのMySQLでパスワードを設定
通常のモードでMySQLを起動する。
# systemctl start mysql
パスワードなしでrootログイン。
# mysql -u root -p
Enter password: <<何も入力せずReturn>>
ALTER USER でパスワードを変更。
mysql> alter user root@localhost identified by '{PASSWORD}';
Query OK, 0 rows affected (0.01 sec) #<-すごい不安になるが、これで反映されている
mysql> exit; #ログアウト
MySQLを再起動する。
# systemctl restart mysql
これで完了。