Skip to content

Commit e7420fd

Browse files
[DPE-1531] Avoid using initialize-insecure and instead use init-file to reset root user's password (#596)
* Avoid using initialize-insecure and instead use init-file to reset root user's password * Address PR feedback
1 parent b353fdb commit e7420fd

File tree

6 files changed

+159
-13
lines changed

6 files changed

+159
-13
lines changed

lib/charms/mysql/v0/mysql.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def wait_until_mysql_connection(self) -> None:
133133
# Increment this major API version when introducing breaking changes
134134
LIBAPI = 0
135135

136-
LIBPATCH = 83
136+
LIBPATCH = 84
137137

138138
UNIT_TEARDOWN_LOCKNAME = "unit-teardown"
139139
UNIT_ADD_LOCKNAME = "unit-add"
@@ -1027,7 +1027,7 @@ def render_mysqld_configuration( # noqa: C901
10271027
config.write(string_io)
10281028
return string_io.getvalue(), dict(config["mysqld"])
10291029

1030-
def configure_mysql_users(self, password_needed: bool = True) -> None:
1030+
def configure_mysql_users(self) -> None:
10311031
"""Configure the MySQL users for the instance."""
10321032
# SYSTEM_USER and SUPER privileges to revoke from the root users
10331033
# Reference: https://dev.mysql.com/doc/refman/8.0/en/privileges-provided.html#priv_super
@@ -1065,13 +1065,10 @@ def configure_mysql_users(self, password_needed: bool = True) -> None:
10651065

10661066
try:
10671067
logger.debug(f"Configuring MySQL users for {self.instance_address}")
1068-
if password_needed:
1069-
self._run_mysqlcli_script(
1070-
configure_users_commands,
1071-
password=self.root_password,
1072-
)
1073-
else:
1074-
self._run_mysqlcli_script(configure_users_commands)
1068+
self._run_mysqlcli_script(
1069+
configure_users_commands,
1070+
password=self.root_password,
1071+
)
10751072
except MySQLClientError:
10761073
logger.error(f"Failed to configure users for: {self.instance_address}")
10771074
raise MySQLConfigureMySQLUsersError

src/charm.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,9 +688,12 @@ def _configure_instance(self, container) -> None:
688688
logger.info("Waiting for instance to be ready")
689689
self._mysql.wait_until_mysql_connection(check_port=False)
690690

691+
logger.info("Resetting root password and starting mysqld")
692+
self._mysql.reset_root_password_and_start_mysqld()
693+
691694
logger.info("Configuring initialized mysqld")
692695
# Configure all base users and revoke privileges from the root users
693-
self._mysql.configure_mysql_users(password_needed=False)
696+
self._mysql.configure_mysql_users()
694697

695698
if self.config.plugin_audit_enabled:
696699
# Enable the audit plugin

src/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
MYSQLD_SOCK_FILE = "/var/run/mysqld/mysqld.sock"
3434
MYSQLSH_SCRIPT_FILE = "/tmp/script.py"
3535
MYSQLD_CONFIG_FILE = "/etc/mysql/mysql.conf.d/z-custom.cnf"
36+
MYSQLD_INIT_CONFIG_FILE = "/etc/mysql/mysql.conf.d/z-custom-init-file.cnf"
3637
MYSQL_LOG_DIR = "/var/log/mysql"
3738
MYSQL_LOG_FILES = [
3839
f"{MYSQL_LOG_DIR}/error.log",

src/mysql_k8s_helpers.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
MYSQL_SYSTEM_GROUP,
4242
MYSQL_SYSTEM_USER,
4343
MYSQLD_DEFAULTS_CONFIG_FILE,
44+
MYSQLD_INIT_CONFIG_FILE,
4445
MYSQLD_LOCATION,
4546
MYSQLD_SERVICE,
4647
MYSQLD_SOCK_FILE,
@@ -57,6 +58,10 @@
5758
from charm import MySQLOperatorCharm
5859

5960

61+
class MySQLResetRootPasswordAndStartMySQLDError(Error):
62+
"""Exception raised when there's an error resetting root password and starting mysqld."""
63+
64+
6065
class MySQLInitialiseMySQLDError(Error):
6166
"""Exception raised when there is an issue initialising an instance."""
6267

@@ -207,7 +212,7 @@ def initialise_mysqld(self) -> None:
207212
"""
208213
bootstrap_command = [
209214
MYSQLD_LOCATION,
210-
"--initialize-insecure",
215+
"--initialize",
211216
"-u",
212217
MYSQL_SYSTEM_USER,
213218
]
@@ -224,6 +229,48 @@ def initialise_mysqld(self) -> None:
224229
self.reset_data_dir()
225230
raise MySQLInitialiseMySQLDError
226231

232+
def reset_root_password_and_start_mysqld(self) -> None:
233+
"""Reset the root user password and start mysqld."""
234+
logger.debug("Resetting root user password and starting mysqld")
235+
alter_user_queries = [
236+
f"ALTER USER 'root'@'localhost' IDENTIFIED BY '{self.root_password}';",
237+
"FLUSH PRIVILEGES;",
238+
]
239+
240+
self.container.push(
241+
"/alter-root-user.sql",
242+
"\n".join(alter_user_queries),
243+
encoding="utf-8",
244+
permissions=0o600,
245+
user=MYSQL_SYSTEM_USER,
246+
group=MYSQL_SYSTEM_GROUP,
247+
)
248+
249+
try:
250+
self.container.push(
251+
MYSQLD_INIT_CONFIG_FILE,
252+
"[mysqld]\ninit_file = /alter-root-user.sql",
253+
encoding="utf-8",
254+
permissions=0o600,
255+
user=MYSQL_SYSTEM_USER,
256+
group=MYSQL_SYSTEM_GROUP,
257+
)
258+
except PathError:
259+
self.container.remove_path("/alter-root-user.sql")
260+
261+
logger.exception("Failed to write the custom config file for init-file")
262+
raise
263+
264+
try:
265+
self.container.restart(MYSQLD_SERVICE)
266+
self.wait_until_mysql_connection(check_port=False)
267+
except (TypeError, MySQLServiceNotRunningError):
268+
logger.exception("Failed to run init-file and wait for connection")
269+
raise
270+
finally:
271+
self.container.remove_path("/alter-root-user.sql")
272+
self.container.remove_path(MYSQLD_INIT_CONFIG_FILE)
273+
227274
@retry(reraise=True, stop=stop_after_delay(120), wait=wait_fixed(2))
228275
def wait_until_mysql_connection(self, check_port: bool = True) -> None:
229276
"""Wait until a connection to MySQL daemon is possible.

tests/unit/test_charm.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,11 @@ def test_on_leader_elected_secrets(self):
151151
)
152152
@patch("mysql_k8s_helpers.MySQL.get_max_connections", return_value=120)
153153
@patch("mysql_k8s_helpers.MySQL.setup_logrotate_config")
154+
@patch("mysql_k8s_helpers.MySQL.reset_root_password_and_start_mysqld")
154155
def test_mysql_pebble_ready(
155156
self,
156157
_,
158+
__,
157159
_get_max_connections,
158160
_get_innodb_buffer_pool_parameters,
159161
_get_member_state,

tests/unit/test_mysql_k8s_helpers.py

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import tenacity
99
from charms.mysql.v0.mysql import MySQLClientError
10-
from ops.pebble import ExecError
10+
from ops.pebble import ExecError, PathError
1111

1212
from mysql_k8s_helpers import (
1313
MYSQLD_SOCK_FILE,
@@ -17,6 +17,7 @@
1717
MySQLDeleteUsersWithLabelError,
1818
MySQLEscalateUserPrivilegesError,
1919
MySQLInitialiseMySQLDError,
20+
MySQLServiceNotRunningError,
2021
MySQLWaitUntilUnitRemovedFromClusterError,
2122
)
2223

@@ -71,7 +72,7 @@ def test_initialise_mysqld(self, _container, _process):
7172
self.mysql.initialise_mysqld()
7273

7374
_container.exec.assert_called_once_with(
74-
command=["/usr/sbin/mysqld", "--initialize-insecure", "-u", "mysql"],
75+
command=["/usr/sbin/mysqld", "--initialize", "-u", "mysql"],
7576
user="mysql",
7677
group="mysql",
7778
)
@@ -420,3 +421,98 @@ def test_update_endpoints(self, _get_cluster_endpoints):
420421
_get_cluster_endpoints.assert_called_once()
421422

422423
_label_pod.assert_has_calls(calls)
424+
425+
@patch("ops.model.Container")
426+
@patch("mysql_k8s_helpers.MySQL.wait_until_mysql_connection")
427+
def test_reset_root_password_and_start_mysqld(self, _wait_until_mysql_connection, _container):
428+
"""Test for reset_root_password_and_start_mysqld()."""
429+
self.mysql.container = _container
430+
self.mysql.reset_root_password_and_start_mysqld()
431+
432+
self.mysql.container.push.assert_has_calls([
433+
call(
434+
"/alter-root-user.sql",
435+
"ALTER USER 'root'@'localhost' IDENTIFIED BY 'password';\nFLUSH PRIVILEGES;",
436+
encoding="utf-8",
437+
permissions=384,
438+
user="mysql",
439+
group="mysql",
440+
),
441+
call(
442+
"/etc/mysql/mysql.conf.d/z-custom-init-file.cnf",
443+
"[mysqld]\ninit_file = /alter-root-user.sql",
444+
encoding="utf-8",
445+
permissions=384,
446+
user="mysql",
447+
group="mysql",
448+
),
449+
])
450+
self.mysql.container.restart.assert_called_once_with("mysqld")
451+
_wait_until_mysql_connection.assert_called_once_with(check_port=False)
452+
self.mysql.container.remove_path.assert_has_calls([
453+
call("/alter-root-user.sql"),
454+
call("/etc/mysql/mysql.conf.d/z-custom-init-file.cnf"),
455+
])
456+
457+
@patch("ops.model.Container")
458+
@patch("mysql_k8s_helpers.MySQL.wait_until_mysql_connection")
459+
def test_reset_root_password_and_start_mysqld_error(
460+
self, _wait_until_mysql_connection, _container
461+
):
462+
"""Test exceptions in reset_root_password_and_start_mysqld()."""
463+
self.mysql.container = _container
464+
_container.push.side_effect = [
465+
None,
466+
PathError("not-found", "Should be a pebble exception"),
467+
]
468+
469+
with self.assertRaises(PathError):
470+
self.mysql.reset_root_password_and_start_mysqld()
471+
472+
self.mysql.container.push.assert_has_calls([
473+
call(
474+
"/alter-root-user.sql",
475+
"ALTER USER 'root'@'localhost' IDENTIFIED BY 'password';\nFLUSH PRIVILEGES;",
476+
encoding="utf-8",
477+
permissions=384,
478+
user="mysql",
479+
group="mysql",
480+
),
481+
])
482+
self.mysql.container.remove_path.assert_called_once_with("/alter-root-user.sql")
483+
_wait_until_mysql_connection.assert_not_called()
484+
485+
_container.push.side_effect = [None, None]
486+
_container.push.reset_mock()
487+
_container.remove_path.reset_mock()
488+
489+
_wait_until_mysql_connection.side_effect = [
490+
MySQLServiceNotRunningError("mysqld not running")
491+
]
492+
493+
with self.assertRaises(MySQLServiceNotRunningError):
494+
self.mysql.reset_root_password_and_start_mysqld()
495+
496+
self.mysql.container.push.assert_has_calls([
497+
call(
498+
"/alter-root-user.sql",
499+
"ALTER USER 'root'@'localhost' IDENTIFIED BY 'password';\nFLUSH PRIVILEGES;",
500+
encoding="utf-8",
501+
permissions=384,
502+
user="mysql",
503+
group="mysql",
504+
),
505+
call(
506+
"/etc/mysql/mysql.conf.d/z-custom-init-file.cnf",
507+
"[mysqld]\ninit_file = /alter-root-user.sql",
508+
encoding="utf-8",
509+
permissions=384,
510+
user="mysql",
511+
group="mysql",
512+
),
513+
])
514+
self.mysql.container.restart.assert_called_once_with("mysqld")
515+
self.mysql.container.remove_path.assert_has_calls([
516+
call("/alter-root-user.sql"),
517+
call("/etc/mysql/mysql.conf.d/z-custom-init-file.cnf"),
518+
])

0 commit comments

Comments
 (0)