diff --git a/src/charm.py b/src/charm.py index 78acd93090..ab1fa0d25e 100755 --- a/src/charm.py +++ b/src/charm.py @@ -2366,6 +2366,7 @@ def update_config( no_peers=no_peers, user_databases_map=self.relations_user_databases_map, slots=replication_slots or None, + instance_password_encryption=self.config.instance_password_encryption, ) if no_peers: return True diff --git a/src/cluster.py b/src/cluster.py index 3a4affb419..8dc99c6131 100644 --- a/src/cluster.py +++ b/src/cluster.py @@ -651,6 +651,7 @@ def render_patroni_yml_file( no_peers: bool = False, user_databases_map: dict[str, str] | None = None, slots: dict[str, str] | None = None, + instance_password_encryption: str | None = None, ) -> None: """Render the Patroni configuration file. @@ -670,6 +671,7 @@ def render_patroni_yml_file( no_peers: Don't include peers. user_databases_map: map of databases to be accessible by each user. slots: replication slots (keys) with assigned database name (values). + instance_password_encryption: algorithm to use to encrypt the users passwords. """ if not self._are_passwords_set: logger.warning("Passwords are not yet generated by the leader") @@ -724,6 +726,7 @@ def render_patroni_yml_file( patroni_password=self.patroni_password, user_databases_map=user_databases_map, slots=slots, + instance_password_encryption=instance_password_encryption, ) self.render_file(f"{PATRONI_CONF_PATH}/patroni.yaml", rendered, 0o600) diff --git a/templates/patroni.yml.j2 b/templates/patroni.yml.j2 index f9a3605fef..d2f42a3b39 100644 --- a/templates/patroni.yml.j2 +++ b/templates/patroni.yml.j2 @@ -181,28 +181,33 @@ postgresql: - {{ 'hostssl' if enable_tls else 'host' }} all +charmed_admin 0.0.0.0/0 scram-sha-256 - {{ 'hostssl' if enable_tls else 'host' }} all +charmed_databases_owner 0.0.0.0/0 scram-sha-256 {%- if not connectivity %} - - {{ 'hostssl' if enable_tls else 'host' }} all all {{ self_ip }} md5 + - {{ 'hostssl' if enable_tls else 'host' }} all all {{ self_ip }} {{ instance_password_encryption }} - {{ 'hostssl' if enable_tls else 'host' }} all all 0.0.0.0/0 reject {%- elif enable_ldap %} - {{ 'hostssl' if enable_tls else 'host' }} all +identity_access 0.0.0.0/0 ldap {{ ldap_parameters }} - - {{ 'hostssl' if enable_tls else 'host' }} all +internal_access 0.0.0.0/0 md5 - {%- for user, databases in user_databases_map.items() %} - - {{ 'hostssl' if enable_tls else 'host' }} {{ databases }} {{ user }} 0.0.0.0/0 md5 - {%- endfor %} + - {{ 'hostssl' if enable_tls else 'host' }} all +internal_access 0.0.0.0/0 {{ instance_password_encryption }} + {%- for user, databases in user_databases_map.items() %} + - {{ 'hostssl' if enable_tls else 'host' }} {{ databases }} {{ user }} 0.0.0.0/0 {{ instance_password_encryption }} + {%- endfor %} {%- else %} - - {{ 'hostssl' if enable_tls else 'host' }} all +internal_access 0.0.0.0/0 md5 - {%- for user, databases in user_databases_map.items() %} + - {{ 'hostssl' if enable_tls else 'host' }} all +internal_access 0.0.0.0/0 scram-sha-256 + {%- for user, databases in user_databases_map.items() %} + {%- if 'pgbouncer_auth_relation_' in user %} - {{ 'hostssl' if enable_tls else 'host' }} {{ databases }} {{ user }} 0.0.0.0/0 md5 - {%- endfor %} + {%- else %} + - {{ 'hostssl' if enable_tls else 'host' }} {{ databases }} {{ user }} 0.0.0.0/0 {{ instance_password_encryption }} + {%- endif %} + {%- endfor %} {%- endif %} - - {{ 'hostssl' if enable_tls else 'host' }} replication replication 127.0.0.1/32 md5 + - {{ 'hostssl' if enable_tls else 'host' }} replication replication 127.0.0.1/32 scram-sha-256 # Allow replications connections from other cluster members. {%- for endpoint in extra_replication_endpoints %} - - {{ 'hostssl' if enable_tls else 'host' }} replication replication {{ endpoint }}/32 md5 + - {{ 'hostssl' if enable_tls else 'host' }} replication replication {{ endpoint }}/32 scram-sha-256 {%- endfor %} {%- for peer_ip in peers_ips %} - - {{ 'hostssl' if enable_tls else 'host' }} replication replication {{ peer_ip }}/0 md5 - {% endfor %} + - {{ 'hostssl' if enable_tls else 'host' }} replication replication {{ peer_ip }}/0 scram-sha-256 + {%- endfor %} + pg_ident: - operator _daemon_ backup authentication: diff --git a/tests/integration/test_config.py b/tests/integration/test_config.py index af36bff263..d22ebe20bf 100644 --- a/tests/integration/test_config.py +++ b/tests/integration/test_config.py @@ -47,6 +47,9 @@ async def test_config_parameters(ops_test: OpsTest, charm) -> None: { "instance_max_locks_per_transaction": ["-1", "64"] }, # config option is between 64 and 2147483647 + { + "instance_password_encryption": [test_string, "md5"] + }, # config option is one of `md5` or `scram-sha-256` { "instance_password_encryption": [test_string, "scram-sha-256"] }, # config option is one of `md5` or `scram-sha-256` diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index d5bee71d67..47b68d7f61 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -1163,6 +1163,7 @@ class _MockSnap: no_peers=False, user_databases_map={"operator": "all", "replication": "all", "rewind": "all"}, slots=None, + instance_password_encryption="scram-sha-256", ) _handle_postgresql_restart_need.assert_called_once_with() _restart_ldap_sync_service.assert_called_once() @@ -1194,6 +1195,7 @@ class _MockSnap: no_peers=False, user_databases_map={"operator": "all", "replication": "all", "rewind": "all"}, slots=None, + instance_password_encryption="scram-sha-256", ) _handle_postgresql_restart_need.assert_called_once() _restart_ldap_sync_service.assert_called_once() diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 0a6d73b928..37231ba075 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -307,6 +307,7 @@ def test_render_patroni_yml_file(peers_ips, patroni): raft_password = "fake-raft-password" patroni_password = "fake-patroni-password" postgresql_version = "16" + instance_password_encryption = "scram-sha-256" # Get the expected content from a file. with open("templates/patroni.yml.j2") as file: @@ -331,6 +332,7 @@ def test_render_patroni_yml_file(peers_ips, patroni): synchronous_node_count=0, raft_password=raft_password, patroni_password=patroni_password, + instance_password_encryption=instance_password_encryption, ) # Setup a mock for the `open` method, set returned data to patroni.yml template. @@ -340,7 +342,9 @@ def test_render_patroni_yml_file(peers_ips, patroni): # Patch the `open` method with our mock. with patch("builtins.open", mock, create=True): # Call the method. - patroni.render_patroni_yml_file() + patroni.render_patroni_yml_file( + instance_password_encryption=instance_password_encryption + ) # Check the template is opened read-only in the call to open. assert mock.call_args_list[0][0] == ("templates/patroni.yml.j2",)