35
35
36
36
# Increment this PATCH version before using `charmcraft publish-lib` or reset
37
37
# to 0 if you are raising the major API version
38
- LIBPATCH = 46
38
+ LIBPATCH = 47
39
+
40
+ # Groups to distinguish HBA access
41
+ ACCESS_GROUP_IDENTITY = "identity_access"
42
+ ACCESS_GROUP_INTERNAL = "internal_access"
43
+ ACCESS_GROUP_RELATION = "relation_access"
44
+
45
+ # List of access groups to filter role assignments by
46
+ ACCESS_GROUPS = [
47
+ ACCESS_GROUP_IDENTITY ,
48
+ ACCESS_GROUP_INTERNAL ,
49
+ ACCESS_GROUP_RELATION ,
50
+ ]
39
51
40
52
# Groups to distinguish database permissions
41
53
PERMISSIONS_GROUP_ADMIN = "admin"
57
69
logger = logging .getLogger (__name__ )
58
70
59
71
72
+ class PostgreSQLAssignGroupError (Exception ):
73
+ """Exception raised when assigning to a group fails."""
74
+
75
+
60
76
class PostgreSQLCreateDatabaseError (Exception ):
61
77
"""Exception raised when creating a database fails."""
62
78
63
79
80
+ class PostgreSQLCreateGroupError (Exception ):
81
+ """Exception raised when creating a group fails."""
82
+
83
+
64
84
class PostgreSQLCreateUserError (Exception ):
65
85
"""Exception raised when creating a user fails."""
66
86
@@ -93,6 +113,10 @@ class PostgreSQLGetPostgreSQLVersionError(Exception):
93
113
"""Exception raised when retrieving PostgreSQL version fails."""
94
114
95
115
116
+ class PostgreSQLListGroupsError (Exception ):
117
+ """Exception raised when retrieving PostgreSQL groups list fails."""
118
+
119
+
96
120
class PostgreSQLListUsersError (Exception ):
97
121
"""Exception raised when retrieving PostgreSQL users list fails."""
98
122
@@ -160,6 +184,24 @@ def _connect_to_database(
160
184
connection .autocommit = True
161
185
return connection
162
186
187
+ def create_access_groups (self ) -> None :
188
+ """Create access groups to distinguish HBA authentication methods."""
189
+ connection = None
190
+ try :
191
+ with self ._connect_to_database () as connection , connection .cursor () as cursor :
192
+ for group in ACCESS_GROUPS :
193
+ cursor .execute (
194
+ SQL ("CREATE ROLE {} NOLOGIN;" ).format (
195
+ Identifier (group ),
196
+ )
197
+ )
198
+ except psycopg2 .Error as e :
199
+ logger .error (f"Failed to create access groups: { e } " )
200
+ raise PostgreSQLCreateGroupError () from e
201
+ finally :
202
+ if connection is not None :
203
+ connection .close ()
204
+
163
205
def create_database (
164
206
self ,
165
207
database : str ,
@@ -321,6 +363,50 @@ def delete_user(self, user: str) -> None:
321
363
logger .error (f"Failed to delete user: { e } " )
322
364
raise PostgreSQLDeleteUserError () from e
323
365
366
+ def grant_internal_access_group_memberships (self ) -> None :
367
+ """Grant membership to the internal access-group to existing internal users."""
368
+ connection = None
369
+ try :
370
+ with self ._connect_to_database () as connection , connection .cursor () as cursor :
371
+ for user in self .system_users :
372
+ cursor .execute (
373
+ SQL ("GRANT {} TO {};" ).format (
374
+ Identifier (ACCESS_GROUP_INTERNAL ),
375
+ Identifier (user ),
376
+ )
377
+ )
378
+ except psycopg2 .Error as e :
379
+ logger .error (f"Failed to grant internal access group memberships: { e } " )
380
+ raise PostgreSQLAssignGroupError () from e
381
+ finally :
382
+ if connection is not None :
383
+ connection .close ()
384
+
385
+ def grant_relation_access_group_memberships (self ) -> None :
386
+ """Grant membership to the relation access-group to existing relation users."""
387
+ rel_users = self .list_users_from_relation ()
388
+ if not rel_users :
389
+ return
390
+
391
+ connection = None
392
+ try :
393
+ with self ._connect_to_database () as connection , connection .cursor () as cursor :
394
+ rel_groups = SQL ("," ).join (Identifier (group ) for group in [ACCESS_GROUP_RELATION ])
395
+ rel_users = SQL ("," ).join (Identifier (user ) for user in rel_users )
396
+
397
+ cursor .execute (
398
+ SQL ("GRANT {groups} TO {users};" ).format (
399
+ groups = rel_groups ,
400
+ users = rel_users ,
401
+ )
402
+ )
403
+ except psycopg2 .Error as e :
404
+ logger .error (f"Failed to grant relation access group memberships: { e } " )
405
+ raise PostgreSQLAssignGroupError () from e
406
+ finally :
407
+ if connection is not None :
408
+ connection .close ()
409
+
324
410
def enable_disable_extensions (
325
411
self , extensions : Dict [str , bool ], database : Optional [str ] = None
326
412
) -> None :
@@ -534,12 +620,34 @@ def is_tls_enabled(self, check_current_host: bool = False) -> bool:
534
620
# Connection errors happen when PostgreSQL has not started yet.
535
621
return False
536
622
623
+ def list_access_groups (self ) -> Set [str ]:
624
+ """Returns the list of PostgreSQL database access groups.
625
+
626
+ Returns:
627
+ List of PostgreSQL database access groups.
628
+ """
629
+ connection = None
630
+ try :
631
+ with self ._connect_to_database () as connection , connection .cursor () as cursor :
632
+ cursor .execute (
633
+ "SELECT groname FROM pg_catalog.pg_group WHERE groname LIKE '%_access';"
634
+ )
635
+ access_groups = cursor .fetchall ()
636
+ return {group [0 ] for group in access_groups }
637
+ except psycopg2 .Error as e :
638
+ logger .error (f"Failed to list PostgreSQL database access groups: { e } " )
639
+ raise PostgreSQLListGroupsError () from e
640
+ finally :
641
+ if connection is not None :
642
+ connection .close ()
643
+
537
644
def list_users (self ) -> Set [str ]:
538
645
"""Returns the list of PostgreSQL database users.
539
646
540
647
Returns:
541
648
List of PostgreSQL database users.
542
649
"""
650
+ connection = None
543
651
try :
544
652
with self ._connect_to_database () as connection , connection .cursor () as cursor :
545
653
cursor .execute ("SELECT usename FROM pg_catalog.pg_user;" )
@@ -548,6 +656,30 @@ def list_users(self) -> Set[str]:
548
656
except psycopg2 .Error as e :
549
657
logger .error (f"Failed to list PostgreSQL database users: { e } " )
550
658
raise PostgreSQLListUsersError () from e
659
+ finally :
660
+ if connection is not None :
661
+ connection .close ()
662
+
663
+ def list_users_from_relation (self ) -> Set [str ]:
664
+ """Returns the list of PostgreSQL database users that were created by a relation.
665
+
666
+ Returns:
667
+ List of PostgreSQL database users.
668
+ """
669
+ connection = None
670
+ try :
671
+ with self ._connect_to_database () as connection , connection .cursor () as cursor :
672
+ cursor .execute (
673
+ "SELECT usename FROM pg_catalog.pg_user WHERE usename LIKE 'relation_id_%';"
674
+ )
675
+ usernames = cursor .fetchall ()
676
+ return {username [0 ] for username in usernames }
677
+ except psycopg2 .Error as e :
678
+ logger .error (f"Failed to list PostgreSQL database users: { e } " )
679
+ raise PostgreSQLListUsersError () from e
680
+ finally :
681
+ if connection is not None :
682
+ connection .close ()
551
683
552
684
def list_valid_privileges_and_roles (self ) -> Tuple [Set [str ], Set [str ]]:
553
685
"""Returns two sets with valid privileges and roles.
0 commit comments