From e52c0efd92862ce1a505547bfe1b6c7b2a7b6876 Mon Sep 17 00:00:00 2001 From: MiaAltieri Date: Wed, 28 Feb 2024 17:50:35 +0000 Subject: [PATCH] Fix password rotate + tests --- lib/charms/mongodb/v1/shards_interface.py | 51 ++++++++++++++++++- .../sharding_tests/test_sharding_backups.py | 4 +- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/lib/charms/mongodb/v1/shards_interface.py b/lib/charms/mongodb/v1/shards_interface.py index 4669974ff..49963ebe3 100644 --- a/lib/charms/mongodb/v1/shards_interface.py +++ b/lib/charms/mongodb/v1/shards_interface.py @@ -55,7 +55,7 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 7 +LIBPATCH = 8 KEYFILE_KEY = "key-file" HOSTS_KEY = "host" OPERATOR_PASSWORD_KEY = MongoDBUser.get_password_key_name_for_user(OperatorUser.get_username()) @@ -466,6 +466,10 @@ def __init__( charm.on[self.relation_name].relation_changed, self._on_relation_changed ) + self.framework.observe( + getattr(self.charm.on, "secret_changed"), self._handle_changed_secrets + ) + self.framework.observe( charm.on[self.relation_name].relation_departed, self.charm.check_relation_broken_or_scale_down, @@ -475,6 +479,51 @@ def __init__( charm.on[self.relation_name].relation_broken, self._on_relation_broken ) + def _handle_changed_secrets(self, event) -> None: + """Update operator and backup user passwords when rotation occurs. + + Changes in secrets do not re-trigger a relation changed event, so it is necessary to listen + to secret changes events. + """ + if ( + not self.charm.unit.is_leader() + or not event.secret.label + or not self.model.get_relation(self.relation_name) + ): + return + + # only one related config-server is possible + config_server_relation = self.charm.model.relations[self.relation_name][0] + + # many secret changed events occur, only listen to those related to our interface with the + # config-server + secret_changing_label = event.secret.label + sharding_secretes_label = f"{self.relation_name}.{config_server_relation.id}.extra.secret" + if secret_changing_label != sharding_secretes_label: + logger.info( + "A secret unrelated to this sharding relation %s is changing, igorning secret changed event.", + str(config_server_relation.id), + ) + return + + operator_password = self.database_requires.fetch_relation_field( + config_server_relation.id, OPERATOR_PASSWORD_KEY + ) + backup_password = self.database_requires.fetch_relation_field( + config_server_relation.id, BACKUP_PASSWORD_KEY + ) + + try: + self.update_password( + username=OperatorUser.get_username(), new_password=operator_password + ) + self.update_password(BackupUser.get_username(), new_password=backup_password) + except RetryError: + self.charm.unit.status = BlockedStatus("Failed to rotate cluster secrets") + logger.error("Shard failed to rotate cluster secrets.") + event.defer() + return + def _on_relation_changed(self, event): """Retrieves secrets from config-server and updates them within the shard.""" if not self.pass_hook_checks(event): diff --git a/tests/integration/sharding_tests/test_sharding_backups.py b/tests/integration/sharding_tests/test_sharding_backups.py index 9fcbb0af0..255c5a586 100644 --- a/tests/integration/sharding_tests/test_sharding_backups.py +++ b/tests/integration/sharding_tests/test_sharding_backups.py @@ -190,12 +190,12 @@ async def test_rotate_backup_password(ops_test: OpsTest) -> None: shard_backup_password = await get_password( ops_test, username="backup", app_name=SHARD_ONE_APP_NAME ) - assert shard_backup_password != new_password, "Application shard-one did not rotate password" + assert shard_backup_password == new_password, "Application shard-one did not rotate password" shard_backup_password = await get_password( ops_test, username="backup", app_name=SHARD_TWO_APP_NAME ) - assert shard_backup_password != new_password, "Application shard-two did not rotate password" + assert shard_backup_password == new_password, "Application shard-two did not rotate password" # verify backup actions work after password rotation leader_unit = await backup_helpers.get_leader_unit(