Skip to content

Commit 0523e6c

Browse files
marceloneppeldragomirprenovate[bot]a-velascoizmalk
authored
Predefined roles compatibility (#960)
* [MISC] Use latest/stable lxd (#804) * Use latest stable lxd * Test tweaks * Test tweaks * Update canonical/data-platform-workflows action to v31.0.1 (#805) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * [DPE-6874] Poll all members in the cluster topology script (#810) * Poll all members in the cluster topology script * Dual branch config * Unit tests and bugfixes * Add peers when starting the observer * Retry sync up checks * [DPE-6572] Add wal_keep_size config option (#799) * Add wal_keep_size config option Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Remove parameter addition Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Reset durability_wal_keep_size value to PG default Signed-off-by: Marcelo Henrique Neppel <[email protected]> --------- Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Create pull_request_template.md (#814) * Create SECURITY.md (#822) * Update README file's security section (#827) * Refactor headings for syntax best practice * Update the Security section * Sync docs from Discourse (#796) Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> * [MISC] Conditional checksum calculation (#812) * Bump boto * Conditional checksum calculation * [DPE-6218] Static code analysis (#828) * Create tiobe_scan.yaml * Remove push trigger * [MISC] Disable landscape subordinate test lxd (#831) * Set series for ubuntu-advantage test and disable the landscape test * Revert to LTS LXD * Update charmcraft.yaml build tools (#815) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * [MISC] Update snapped PostgreSQL (#832) * [DPE-6345] LDAP I: Create access groups (#823) * [DPE-6345] LDAP II: Include charm libs (#824) * [DPE-6345] LDAP III: Define config and handlers (#825) * [DPE-6345] LDAP IV: Define snap service (#838) * [DPE-6345] LDAP V: Define mapping option (#849) * [MISC] Disable network cut tests on arm (#844) * Disable network cut tests on arm * Back to LXD 5 * [DPE-6815] disable pgaudit during extensions changes (#842) * disable pgaudit during extensions changes * Bump libs * Lock file maintenance Python dependencies (main) (#816) * Lock file maintenance Python dependencies * Fix linting --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Dragomir Penev <[email protected]> * Update dependency uv to v0.6.16 (#847) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * [DPE-6664] Make username mandatory in set-password (#846) * Make username mandatory * Second get password method * Default in get-password * Add conditional expose directive (#853) * Lock file maintenance Python dependencies (#854) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Mandatory scope for promote action (#856) * Update charmcraft.yaml build tools (#860) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Lock file maintenance Python dependencies (#861) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Sync docs from Discourse (#850) Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> * [MISC] Extend relation-user listing syntax (#868) * Move _update_member_ip call to correctly remove Raft cluster member when network is cut Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix coverage Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Sync libs (#884) * Update refresh tests to modify charm to ensure refresh off edge or stable * Fix lint warnings * Store temporary charms in /tmp for upgrade_from_stable tests * Use force-refresh-start instead of forcing refresh by updating versions * Remove runner password (#913) * [DPE-6898] User->databases pg_hba rules (#885) * Restrict each user to their allowed databases Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix unit tests Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix sync users on replicas Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix unit test Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Add default landscape user permission Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Increase sleep time in pg_hba test, fix user->database mapping for upgrade from stable and skip event trigger function code when not a superuser Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Improve users list check Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix raft reinitialisation in tests Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Decrease the amount of API calls by one Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Check users list directly Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Tweak test fast interval Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Improvements to avoid replica restart while syncing from primary Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix linting Signed-off-by: Marcelo Henrique Neppel <[email protected]> --------- Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Handle same snap revision situation in upgrade tests Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Merge Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Improvement to avoid replica restart while syncing from primary Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Run stop-continuous-writes action only once Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Increase sleep time in Juju spaces test Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Update charmcraft.yaml build tools (#871) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * [MISC] Remove JujuVersion warning in 14/edge (#933) * Remove JujuVersion warning * Update libs * Refactor v14 documentation for Sphinx (#919) * initial starter pack transfer * update conf.py * import processed discourse pages * fix internal references and some broken URLs * fix some style errors * organize navigation * remove h1 heading anchors * add images to repository * edit home page * update .readthedocs.yaml * remove docs/requirements.txt from .gitignore scope * fix incorrect paths * remove shell syntax from code blocks * remove juju 2 banners * fix dropdown formatting * fix and polish admonitions, collapsible, and misc formatting * remove v16 docs * remove reference to nonexistant page * Join all tutorial pages * rename how-to-guides to how-to * polish cloud deployment guides and rename leftover how-to-guide references * polish and sync how-to guides with k8s * remove discourse sync workflow * specify channel on all deploy commands * misc polishing, add version to side nav * add pg 16 admonitions * ignore docs folder in charm workflows * sync misc. pages with k8s * Minor README update with new documentation link * add new section to CLI-helpers reference * pin commit for v16 tag on markdown lint workflow for added security * Update README.md * remove sphinx python dependency check workflow * Update index.md: add link to roles.md (#928) --------- Co-authored-by: Marcelo Henrique Neppel <[email protected]> Co-authored-by: Alex Lutay <[email protected]> * [DPE-7511] Fix the auth username pattern (#941) * Fix auth username pattern Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix another ocurrence of the pattern Signed-off-by: Marcelo Henrique Neppel <[email protected]> --------- Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Add temp tablespace create * Temp tblspace outside of transaction * Reset role in test_pg_hba setup Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Detect when databases and their ACLs change Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix predefined catalog roles test Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix unit test Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Add unit tests Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Add missing autocommit Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Remove _hash suffix from variables names Signed-off-by: Marcelo Henrique Neppel <[email protected]> * [MISC] Fix timeouts in 14 to 16 merge (#959) * Handle PostgreSQLListUsersError * Try to trigger the pg_hba update on db requested * Try to hold db requested until pg_hba is up to date * Increase timeouts * Scale in parallel * Fix param passing * Increase timeout * Try to scale without ffwd * Try not to defer rel changed * Remove extra hook * Check if patroni is running before calling the health endpoint * Revert timeout * Pass the timeout param --------- Signed-off-by: Marcelo Henrique Neppel <[email protected]> Co-authored-by: Dragomir Penev <[email protected]> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Andreia <[email protected]> Co-authored-by: Vladimir Izmalkov <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Sinclert Pérez <[email protected]> Co-authored-by: Dragomir Penev <[email protected]> Co-authored-by: swetha1654 <[email protected]> Co-authored-by: Shayan Patel <[email protected]> Co-authored-by: Alex Lutay <[email protected]>
1 parent e15d644 commit 0523e6c

25 files changed

+797
-103
lines changed

.github/workflows/ci.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ on:
1414
- 'LICENSE'
1515
- '**.md'
1616
- .github/renovate.json5
17-
- '.github/workflows/sync_docs.yaml'
1817
- 'docs/**'
1918
schedule:
2019
- cron: '53 0 * * *' # Daily at 00:53 UTC

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build/
66
__pycache__/
77
*.py[cod]
88
coverage.xml
9-
requirements.txt
9+
/requirements.txt
1010
requirements-last-build.txt
1111
.last_refresh_unit_status.json
1212

README.md

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,14 @@
55
[![Tests](https://github.com/canonical/postgresql-operator/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/canonical/postgresql-operator/actions/workflows/ci.yaml?query=branch%3Amain)
66
[![codecov](https://codecov.io/gh/canonical/postgresql-operator/graph/badge.svg?token=4V2mu7aWmu)](https://codecov.io/gh/canonical/postgresql-operator)
77

8-
## Description
98

10-
This repository contains a [Juju Charm](https://charmhub.io/postgresql) for deploying [PostgreSQL](https://www.postgresql.org/about/) on virtual machines ([LXD](https://ubuntu.com/lxd)).
11-
To deploy on Kubernetes, please use [Charmed PostgreSQL K8s Operator](https://charmhub.io/postgresql-k8s).
9+
This repository contains a charmed operator for deploying [PostgreSQL](https://www.postgresql.org/about/) on virtual machines via the [Juju orchestration engine](https://juju.is/).
1210

13-
This operator provides a PostgreSQL database with replication enabled: one primary instance and one (or more) hot standby replicas. The Operator in this repository is a Python script which wraps PostgreSQL versions distributed by Ubuntu Jammy series and adding [Patroni](https://github.com/zalando/patroni) on top of it, providing lifecycle management and handling events (install, configure, integrate, remove, etc).
11+
To learn more about how to deploy and operate Charmed PostgreSQL, see the [official documentation](https://canonical-charmed-postgresql.readthedocs-hosted.com/).
1412

15-
## README contents
13+
## Overview
1614

17-
* [Basic usage](#basic-usage): Deploy and scale Charmerd PostgreSQL
18-
* [Integrations](#integrations-relations): Supported interfaces for integrations
19-
* [Contributing](#contributing)
20-
* [Licensing and trademark](#licensing-and-trademark)
15+
This operator provides a PostgreSQL database with replication enabled: one primary instance and one (or more) hot standby replicas. The Operator in this repository is a Python script which wraps PostgreSQL versions distributed by Ubuntu Jammy series and adding [Patroni](https://github.com/zalando/patroni) on top of it, providing lifecycle management and handling events (install, configure, integrate, remove, etc).
2116

2217
## Basic usage
2318

lib/charms/postgresql_k8s/v1/postgresql.py

Lines changed: 210 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ class PostgreSQLGetPostgreSQLVersionError(Exception):
115115
"""Exception raised when retrieving PostgreSQL version fails."""
116116

117117

118+
class PostgreSQLListAccessibleDatabasesForUserError(Exception):
119+
"""Exception raised when retrieving the accessible databases for a user fails."""
120+
121+
118122
class PostgreSQLListGroupsError(Exception):
119123
"""Exception raised when retrieving PostgreSQL groups list fails."""
120124

@@ -200,6 +204,11 @@ def create_access_groups(self) -> None:
200204
try:
201205
with self._connect_to_database() as connection, connection.cursor() as cursor:
202206
for group in ACCESS_GROUPS:
207+
cursor.execute(
208+
SQL("SELECT TRUE FROM pg_roles WHERE rolname={};").format(Literal(group))
209+
)
210+
if cursor.fetchone() is not None:
211+
continue
203212
cursor.execute(
204213
SQL("CREATE ROLE {} NOLOGIN;").format(
205214
Identifier(group),
@@ -271,11 +280,7 @@ def create_user(
271280
roles = privileges = None
272281
if extra_user_roles:
273282
valid_privileges, valid_roles = self.list_valid_privileges_and_roles()
274-
roles = [
275-
role
276-
for role in extra_user_roles
277-
if role in valid_roles
278-
]
283+
roles = [role for role in extra_user_roles if role in valid_roles]
279284
privileges = {
280285
extra_user_role
281286
for extra_user_role in extra_user_roles
@@ -297,7 +302,9 @@ def create_user(
297302
user_definition = "ALTER ROLE {} "
298303
else:
299304
user_definition = "CREATE ROLE {} "
300-
user_definition += f"WITH LOGIN{' SUPERUSER' if admin else ''} ENCRYPTED PASSWORD '{password}'"
305+
user_definition += (
306+
f"WITH LOGIN{' SUPERUSER' if admin else ''} ENCRYPTED PASSWORD '{password}'"
307+
)
301308
if in_role:
302309
user_definition += f" IN ROLE {in_role}"
303310
if can_create_database:
@@ -353,7 +360,7 @@ def create_predefined_instance_roles(self) -> None:
353360
],
354361
ROLE_DBA: [
355362
f"CREATE ROLE {ROLE_DBA} NOSUPERUSER CREATEDB NOCREATEROLE NOLOGIN NOREPLICATION;"
356-
]
363+
],
357364
}
358365

359366
_, existing_roles = self.list_valid_privileges_and_roles()
@@ -621,15 +628,22 @@ def is_tls_enabled(self, check_current_host: bool = False) -> bool:
621628
# Connection errors happen when PostgreSQL has not started yet.
622629
return False
623630

624-
def list_access_groups(self) -> Set[str]:
631+
def list_access_groups(self, current_host=False) -> Set[str]:
625632
"""Returns the list of PostgreSQL database access groups.
626633
634+
Args:
635+
current_host: whether to check the current host
636+
instead of the primary host.
637+
627638
Returns:
628639
List of PostgreSQL database access groups.
629640
"""
630641
connection = None
642+
host = self.current_host if current_host else None
631643
try:
632-
with self._connect_to_database() as connection, connection.cursor() as cursor:
644+
with self._connect_to_database(
645+
database_host=host
646+
) as connection, connection.cursor() as cursor:
633647
cursor.execute(
634648
"SELECT groname FROM pg_catalog.pg_group WHERE groname LIKE '%_access';"
635649
)
@@ -642,16 +656,69 @@ def list_access_groups(self) -> Set[str]:
642656
if connection is not None:
643657
connection.close()
644658

645-
def list_users(self) -> Set[str]:
659+
def list_accessible_databases_for_user(self, user: str, current_host=False) -> Set[str]:
660+
"""Returns the list of accessible databases for a specific user.
661+
662+
Args:
663+
user: the user to check.
664+
current_host: whether to check the current host
665+
instead of the primary host.
666+
667+
Returns:
668+
List of accessible database (the ones where
669+
the user has the CONNECT privilege).
670+
"""
671+
connection = None
672+
host = self.current_host if current_host else None
673+
try:
674+
with self._connect_to_database(
675+
database_host=host
676+
) as connection, connection.cursor() as cursor:
677+
cursor.execute(
678+
SQL(
679+
"SELECT TRUE FROM pg_catalog.pg_user WHERE usename = {} AND usesuper;"
680+
).format(Literal(user))
681+
)
682+
if cursor.fetchone() is not None:
683+
return {"all"}
684+
cursor.execute(
685+
SQL(
686+
"SELECT datname FROM pg_catalog.pg_database WHERE has_database_privilege({}, datname, 'CONNECT') AND NOT datistemplate;"
687+
).format(Literal(user))
688+
)
689+
databases = cursor.fetchall()
690+
return {database[0] for database in databases}
691+
except psycopg2.Error as e:
692+
logger.error(f"Failed to list accessible databases for user {user}: {e}")
693+
raise PostgreSQLListAccessibleDatabasesForUserError() from e
694+
finally:
695+
if connection is not None:
696+
connection.close()
697+
698+
def list_users(self, group: Optional[str] = None, current_host=False) -> Set[str]:
646699
"""Returns the list of PostgreSQL database users.
647700
701+
Args:
702+
group: optional group to filter the users.
703+
current_host: whether to check the current host
704+
instead of the primary host.
705+
648706
Returns:
649707
List of PostgreSQL database users.
650708
"""
651709
connection = None
710+
host = self.current_host if current_host else None
652711
try:
653-
with self._connect_to_database() as connection, connection.cursor() as cursor:
654-
cursor.execute("SELECT usename FROM pg_catalog.pg_user;")
712+
with self._connect_to_database(
713+
database_host=host
714+
) as connection, connection.cursor() as cursor:
715+
if group:
716+
query = SQL(
717+
"SELECT usename FROM (SELECT UNNEST(grolist) AS user_id FROM pg_catalog.pg_group WHERE groname = {}) AS g JOIN pg_catalog.pg_user AS u ON g.user_id = u.usesysid;"
718+
).format(Literal(group))
719+
else:
720+
query = "SELECT usename FROM pg_catalog.pg_user;"
721+
cursor.execute(query)
655722
usernames = cursor.fetchall()
656723
return {username[0] for username in usernames}
657724
except psycopg2.Error as e:
@@ -661,19 +728,27 @@ def list_users(self) -> Set[str]:
661728
if connection is not None:
662729
connection.close()
663730

664-
def list_users_from_relation(self) -> Set[str]:
731+
def list_users_from_relation(self, current_host=False) -> Set[str]:
665732
"""Returns the list of PostgreSQL database users that were created by a relation.
666733
734+
Args:
735+
current_host: whether to check the current host
736+
instead of the primary host.
737+
667738
Returns:
668739
List of PostgreSQL database users.
669740
"""
670741
connection = None
742+
host = self.current_host if current_host else None
671743
try:
672-
with self._connect_to_database() as connection, connection.cursor() as cursor:
744+
with self._connect_to_database(
745+
database_host=host
746+
) as connection, connection.cursor() as cursor:
673747
cursor.execute(
674748
"SELECT usename "
675749
"FROM pg_catalog.pg_user "
676-
"WHERE usename LIKE 'relation_id_%' OR usename LIKE 'relation-%';"
750+
"WHERE usename LIKE 'relation_id_%' OR usename LIKE 'relation-%' "
751+
"OR usename LIKE 'pgbouncer_auth_relation_%' OR usename LIKE '%_user_%_%';"
677752
)
678753
usernames = cursor.fetchall()
679754
return {username[0] for username in usernames}
@@ -713,24 +788,113 @@ def set_up_database(self, temp_location: Optional[str] = None) -> None:
713788
cursor.execute(f"CREATE TABLESPACE temp LOCATION '{temp_location}';")
714789
cursor.execute("GRANT CREATE ON TABLESPACE temp TO public;")
715790

716-
# Allow access to the postgres database only to the system users.
717-
cursor.execute("REVOKE ALL PRIVILEGES ON DATABASE postgres FROM PUBLIC;")
718-
cursor.execute("REVOKE CREATE ON SCHEMA public FROM PUBLIC;")
719-
for user in self.system_users:
791+
cursor.close()
792+
cursor = None
793+
connection.close()
794+
connection = None
795+
796+
with self._connect_to_database(
797+
database="template1"
798+
) as connection, connection.cursor() as cursor:
720799
cursor.execute(
721-
SQL("GRANT ALL PRIVILEGES ON DATABASE postgres TO {};").format(
722-
Identifier(user)
723-
)
800+
"SELECT TRUE FROM pg_roles WHERE rolname='charmed_databases_owner';"
724801
)
802+
if cursor.fetchone() is None:
803+
self.create_user(
804+
"charmed_databases_owner",
805+
can_create_database=True,
806+
)
725807

726-
cursor.execute("SELECT TRUE FROM pg_roles WHERE rolname='charmed_databases_owner';")
727-
if cursor.fetchone() is None:
728-
self.create_user(
729-
"charmed_databases_owner", can_create_database=True,
808+
self.set_up_login_hook_function()
809+
self.set_up_predefined_catalog_roles_function()
810+
811+
# Create database function and event trigger to identify users created by PgBouncer.
812+
cursor.execute(
813+
"SELECT TRUE FROM pg_event_trigger WHERE evtname = 'update_pg_hba_on_create_schema';"
730814
)
815+
if cursor.fetchone() is None:
816+
cursor.execute("""
817+
CREATE OR REPLACE FUNCTION update_pg_hba()
818+
RETURNS event_trigger
819+
LANGUAGE plpgsql
820+
AS $$
821+
DECLARE
822+
hba_file TEXT;
823+
copy_command TEXT;
824+
connection_type TEXT;
825+
rec record;
826+
insert_value TEXT;
827+
changes INTEGER = 0;
828+
BEGIN
829+
-- Don't execute on replicas.
830+
IF NOT pg_is_in_recovery() THEN
831+
-- Load the current authorisation rules.
832+
DROP TABLE IF EXISTS pg_hba;
833+
CREATE TEMPORARY TABLE pg_hba (lines TEXT);
834+
SELECT setting INTO hba_file FROM pg_settings WHERE name = 'hba_file';
835+
IF hba_file IS NOT NULL THEN
836+
copy_command='COPY pg_hba FROM ''' || hba_file || '''' ;
837+
EXECUTE copy_command;
838+
-- Build a list of the relation users and the databases they can access.
839+
DROP TABLE IF EXISTS relation_users;
840+
CREATE TEMPORARY TABLE relation_users AS
841+
SELECT t.user, STRING_AGG(DISTINCT t.database, ',') AS databases FROM( SELECT u.usename AS user, CASE WHEN u.usesuper THEN 'all' ELSE d.datname END AS database FROM ( SELECT usename, usesuper FROM pg_catalog.pg_user WHERE usename NOT IN ('backup', 'monitoring', 'operator', 'postgres', 'replication', 'rewind')) AS u JOIN ( SELECT datname FROM pg_catalog.pg_database WHERE NOT datistemplate ) AS d ON has_database_privilege(u.usename, d.datname, 'CONNECT') ) AS t GROUP BY 1;
842+
IF (SELECT COUNT(lines) FROM pg_hba WHERE lines LIKE 'hostssl %') > 0 THEN
843+
connection_type := 'hostssl';
844+
ELSE
845+
connection_type := 'host';
846+
END IF;
847+
-- Add the new users to the pg_hba file.
848+
FOR rec IN SELECT * FROM relation_users
849+
LOOP
850+
insert_value := connection_type || ' ' || rec.databases || ' ' || rec.user || ' 0.0.0.0/0 md5';
851+
IF (SELECT COUNT(lines) FROM pg_hba WHERE lines = insert_value) = 0 THEN
852+
INSERT INTO pg_hba (lines) VALUES (insert_value);
853+
changes := changes + 1;
854+
END IF;
855+
END LOOP;
856+
-- Remove users that don't exist anymore from the pg_hba file.
857+
FOR rec IN SELECT h.lines FROM pg_hba AS h LEFT JOIN relation_users AS r ON SPLIT_PART(h.lines, ' ', 3) = r.user WHERE r.user IS NULL AND (SPLIT_PART(h.lines, ' ', 3) LIKE 'relation_id_%' OR SPLIT_PART(h.lines, ' ', 3) LIKE 'pgbouncer_auth_relation_%' OR SPLIT_PART(h.lines, ' ', 3) LIKE '%_user_%_%')
858+
LOOP
859+
DELETE FROM pg_hba WHERE lines = rec.lines;
860+
changes := changes + 1;
861+
END LOOP;
862+
-- Apply the changes to the pg_hba file.
863+
IF changes > 0 THEN
864+
copy_command='COPY pg_hba TO ''' || hba_file || '''' ;
865+
EXECUTE copy_command;
866+
PERFORM pg_reload_conf();
867+
END IF;
868+
END IF;
869+
END IF;
870+
END;
871+
$$;
872+
""")
873+
cursor.execute("""
874+
CREATE EVENT TRIGGER update_pg_hba_on_create_schema
875+
ON ddl_command_end
876+
WHEN TAG IN ('CREATE SCHEMA')
877+
EXECUTE FUNCTION update_pg_hba();
878+
""")
879+
cursor.execute("""
880+
CREATE EVENT TRIGGER update_pg_hba_on_drop_schema
881+
ON ddl_command_end
882+
WHEN TAG IN ('DROP SCHEMA')
883+
EXECUTE FUNCTION update_pg_hba();
884+
""")
885+
886+
connection.close()
887+
connection = None
731888

732-
self.set_up_login_hook_function()
733-
self.set_up_predefined_catalog_roles_function()
889+
with self._connect_to_database() as connection, connection.cursor() as cursor:
890+
cursor.execute("REVOKE ALL PRIVILEGES ON DATABASE postgres FROM PUBLIC;")
891+
cursor.execute("REVOKE CREATE ON SCHEMA public FROM PUBLIC;")
892+
for user in self.system_users:
893+
cursor.execute(
894+
SQL("GRANT ALL PRIVILEGES ON DATABASE postgres TO {};").format(
895+
Identifier(user)
896+
)
897+
)
734898
except psycopg2.Error as e:
735899
logger.error(f"Failed to set up databases: {e}")
736900
raise PostgreSQLDatabasesSetupError() from e
@@ -1082,3 +1246,21 @@ def validate_group_map(self, group_map: Optional[str]) -> bool:
10821246
return False
10831247

10841248
return True
1249+
1250+
def is_user_in_hba(self, username: str) -> bool:
1251+
"""Check if user was added in pg_hba."""
1252+
connection = None
1253+
try:
1254+
with self._connect_to_database() as connection, connection.cursor() as cursor:
1255+
cursor.execute(
1256+
SQL(
1257+
"SELECT COUNT(*) FROM pg_hba_file_rules WHERE {} = ANY(user_name);"
1258+
).format(Literal(username))
1259+
)
1260+
return cursor.fetchone()[0] > 0
1261+
except psycopg2.Error as e:
1262+
logger.debug(f"Failed to check pg_hba: {e}")
1263+
return False
1264+
finally:
1265+
if connection:
1266+
connection.close()

0 commit comments

Comments
 (0)