22
33import argparse
44import json
5-
65import boto3
76from rich .console import Console
87from botocore .exceptions import ClientError
98
109parser = argparse .ArgumentParser ()
1110parser .add_argument (
1211 "--stack-name" ,
13- help = "the name of the CloudFormation stack containing the cd2 Aurora database" ,
12+ help = "The name of the CloudFormation stack containing the Aurora database" ,
1413 required = True ,
1514)
16-
1715args = parser .parse_args ()
1816
1917console = Console ()
2018
19+ # Initialize AWS clients
2120secrets_client = boto3 .client ("secretsmanager" )
2221rds_data_client = boto3 .client ("rds-data" )
23- cf_reource = boto3 .resource ("cloudformation" )
24- stack = cf_reource .Stack (args .stack_name )
22+ cf_resource = boto3 .resource ("cloudformation" )
23+ stack = cf_resource .Stack (args .stack_name )
2524
2625console .print ("Starting database preparation" , style = "bold green" )
2726
28- # read the outputs and parameters from the Cloudformation stack
29- stack_outputs = {
30- output ["OutputKey" ]: output ["OutputValue" ]
31- for output in stack .outputs
32- }
33- stack_parameters = {
34- parameter ["ParameterKey" ]: parameter ["ParameterValue" ]
35- for parameter in stack .parameters
36- }
27+ # Fetch stack outputs and parameters
28+ stack_outputs = {output ["OutputKey" ]: output ["OutputValue" ] for output in stack .outputs }
29+ stack_parameters = {parameter ["ParameterKey" ]: parameter ["ParameterValue" ] for parameter in stack .parameters }
3730
38- # get the database admin secret
31+ # Get admin and cluster details
3932admin_secret_arn = stack_outputs ["AdminSecretArn" ]
40- admin_secret = json .loads (
41- secrets_client .get_secret_value (SecretId = admin_secret_arn )["SecretString" ]
42- )
33+ admin_secret = json .loads (secrets_client .get_secret_value (SecretId = admin_secret_arn )["SecretString" ])
4334admin_username = admin_secret ["username" ]
44-
45- # get the database cluster ARN
4635aurora_cluster_arn = stack_outputs ["AuroraClusterArn" ]
4736
48- # get the environment
37+ # Get environment and resource prefix
4938env = stack_parameters ["EnvironmentParameter" ]
50-
51- # get the resource prefix
5239prefix = stack_parameters ["ResourcePrefixParameter" ]
5340
54- # get the database user secrets
55- secret_name_prefix = f" { prefix } -cd2-db-user- { env } -"
56- user_secrets = secrets_client . list_secrets (
57- Filters = [{ "Key " : "name" , "Values" : [ secret_name_prefix ]}] ,
58- MaxResults = 100 ,
59- )
41+ # Define role-based privileges
42+ role_privileges = {
43+ "read_only" : "GRANT SELECT ON ALL TABLES IN SCHEMA {schema_name} TO {username};" ,
44+ "read_write " : "GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA {schema_name} TO {username};" ,
45+ "admin" : "GRANT ALL PRIVILEGES ON SCHEMA {schema_name} TO {username};" ,
46+ }
6047
61- # for each user secret, create the database user and schema
62- for s in user_secrets ["SecretList" ]:
63- secret_arn = s ["ARN" ]
48+ # Define user-role mapping
49+ user_roles = {
50+ "athena" : "read_only" ,
51+ "canvas" : "admin"
52+ }
6453
65- secret_value = json .loads (
66- secrets_client .get_secret_value (SecretId = secret_arn )["SecretString" ]
67- )
68- password = secret_value ["password" ]
69- username = secret_value ["username" ]
70- database_name = secret_value ["dbname" ]
71- schema_name = username
54+ # List of usernames that should have schemas created
55+ users_to_create_schema = ["canvas" ]
7256
73- console .print (
74- f" - creating database user [bold]{ username } [/bold] in database [bold]{ database_name } [/bold]" ,
75- style = "green" ,
57+ def get_user_role (username ):
58+ """Retrieve the role for a given username. Return read-only if not found"""
59+ return user_roles .get (username , "read_only" )
60+
61+ def execute_statement (sql , database_name ):
62+ rds_data_client .execute_statement (
63+ resourceArn = aurora_cluster_arn ,
64+ secretArn = admin_secret_arn ,
65+ sql = sql ,
66+ database = database_name ,
7667 )
7768
78- # create the database user
69+ def create_user (username , password , database_name ):
70+ """Create a user"""
7971 try :
80- user_sql = f"CREATE USER { username } WITH PASSWORD '{ password } ' LOGIN"
81- rds_data_client .execute_statement (
82- resourceArn = aurora_cluster_arn ,
83- secretArn = admin_secret_arn ,
84- sql = user_sql ,
85- database = database_name ,
86- )
87- console .print (" - Created user" , style = "bold green" )
72+ create_user_sql = f"CREATE USER { username } WITH PASSWORD '{ password } '"
73+ execute_statement (create_user_sql , database_name )
74+ console .print (f" - Created user { username } " , style = "bold green" )
8875 except ClientError as e :
8976 if "already exists" in e .response ["Error" ]["Message" ]:
90- console .print (f" - User { username } already exists" , style = "bold red" )
91-
92- try :
93- change_sql = f"ALTER USER { username } WITH PASSWORD '{ password } '"
94- rds_data_client .execute_statement (
95- resourceArn = aurora_cluster_arn ,
96- secretArn = admin_secret_arn ,
97- sql = change_sql ,
98- database = database_name ,
99- )
100- console .print (
101- f" - Updated password for user { username } " , style = "bold green"
102- )
103- except ClientError as e :
104- console .print (
105- f" ! Unexpected error when updating password for { username } : { e } " , style = "bold red"
106- )
107- continue
77+ console .print (f" - User { username } already exists. Updating password..." , style = "yellow" )
78+ update_password_sql = f"ALTER USER { username } WITH PASSWORD '{ password } '"
79+ execute_statement (update_password_sql , database_name )
80+ console .print (f" - Updated password for user { username } " , style = "green" )
10881 else :
109- console .print (
110- f" ! Unexpected error when creating user { username } : { e } " , style = "bold red"
111- )
112- continue
82+ console .print (f" ! Error creating user { username } : { e } " , style = "bold red" )
11383
114- # Grant the role to the admin user
84+ def create_schema (schema_name , username , database_name ):
85+ """Create a schema with user as owner"""
11586 try :
116- grant_sql = f"GRANT { username } TO { admin_username } "
117- rds_data_client .execute_statement (
118- resourceArn = aurora_cluster_arn ,
119- secretArn = admin_secret_arn ,
120- sql = grant_sql ,
121- database = database_name ,
122- )
123- console .print (
124- f" - Granted user { username } to { admin_username } " , style = "bold green"
125- )
87+ create_schema_sql = f"CREATE SCHEMA IF NOT EXISTS { username } AUTHORIZATION { username } "
88+ execute_statement (create_schema_sql , database_name )
89+ console .print (f" - Created schema { schema_name } with owner { username } " , style = "bold green" )
12690 except ClientError as e :
127- console .print (f" ! Unexpected error granting { username } role to { admin_username } : { e } " , style = "bold red" )
128- continue
91+ if "already exists" in e .response ["Error" ]["Message" ]:
92+ console .print (f" - Schema { schema_name } already exists" , style = "yellow" )
93+ else :
94+ console .print (f" ! Error creating schema { schema_name } with owner { username } : { e } " , style = "bold red" )
12995
130- # create the schema
96+ def assign_privileges (username , schema_name , role , database_name ):
97+ """Assign privileges to a database user based on their role."""
13198 try :
132- schema_sql = f"CREATE SCHEMA IF NOT EXISTS AUTHORIZATION { username } "
133- rds_data_client .execute_statement (
134- resourceArn = aurora_cluster_arn ,
135- secretArn = admin_secret_arn ,
136- sql = schema_sql ,
137- database = database_name ,
138- )
139- console .print (f" - Created schema [bold]{ username } [/bold]" , style = "green" )
99+ grant_schema_sql = role_privileges [role ].format (username = username , schema_name = schema_name )
100+ execute_statement (grant_schema_sql , database_name )
101+ console .print (f" - Granted { role } privileges on schema { schema_name } to user { username } " , style = "bold green" )
140102 except ClientError as e :
141- console .print (f" ! Unexpected error creating schema { username } : { e } " , style = "bold red" )
142- continue
103+ console .print (f" ! Error granting { role } privileges on schema { schema_name } to { username } : { e } " , style = "bold red" )
143104
144- # create the instructure_dap schema
105+ def grant_usage_to_schema (username , schema_name , database_name ):
106+ """Grant usage on a schema to a user"""
145107 try :
146- schema_sql = f"CREATE SCHEMA IF NOT EXISTS instructure_dap AUTHORIZATION { username } "
147- rds_data_client .execute_statement (
148- resourceArn = aurora_cluster_arn ,
149- secretArn = admin_secret_arn ,
150- sql = schema_sql ,
151- database = database_name ,
152- )
153- console .print (
154- f" - Created schema [bold]instructure_dap[/bold] in database [bold]{ database_name } [/bold]" ,
155- style = "green" ,
156- )
108+ grant_usage_sql = f"GRANT USAGE ON SCHEMA { schema_name } TO { username } "
109+ execute_statement (grant_usage_sql , database_name )
110+ console .print (f" - Granted usage on schema { schema_name } to user { username } " , style = "bold green" )
157111 except ClientError as e :
158- console .print (f" ! Unexpected error: { e } " , style = "bold red" )
159- continue
112+ console .print (f" ! Error granting usage on schema { schema_name } to { username } : { e } " , style = "bold red" )
160113
161- # grant create permission on database to canvas user
114+ def grant_user_to_admin (username , admin_username , database_name ):
115+ """Grant user to the admin user"""
162116 try :
163- grant_sql = f"GRANT CREATE ON DATABASE { database_name } TO { username } "
164- rds_data_client .execute_statement (
165- resourceArn = aurora_cluster_arn ,
166- secretArn = admin_secret_arn ,
167- sql = grant_sql ,
168- database = "postgres" ,
169- )
170- console .print (
171- f" - Granted CREATE on database { database_name } to user { username } " ,
172- style = "bold green" ,
173- )
117+ grant_user_sql = f"GRANT { username } TO { admin_username } "
118+ execute_statement (grant_user_sql , database_name )
119+ console .print (f" - Granted user { username } to user { admin_username } " , style = "bold green" )
174120 except ClientError as e :
175- console .print (f" ! Unexpected error: { e } " , style = "bold red" )
121+ console .print (f" ! Error granting user { username } to user { admin_username } : { e } " , style = "bold red" )
122+
123+ # Get all database user secrets
124+ secret_name_prefix = f"{ prefix } -cd2-db-user-{ env } -"
125+ user_secrets = secrets_client .list_secrets (
126+ Filters = [{"Key" : "name" , "Values" : [secret_name_prefix ]}],
127+ MaxResults = 100 ,
128+ )
129+
130+ # Process each user secret to create database users, schemas, and assign roles
131+ for s in user_secrets ["SecretList" ]:
132+ secret_arn = s ["ARN" ]
133+ secret_value = json .loads (secrets_client .get_secret_value (SecretId = secret_arn )["SecretString" ])
134+ username = secret_value ["username" ]
135+ database_name = secret_value ["dbname" ]
136+
137+ # Create or update the user
138+ create_user (username , secret_value ["password" ], database_name )
139+
140+ # Grant user to admin user
141+ grant_user_to_admin (username , admin_username , database_name )
142+
143+ # Create schema for user (with them as owner) if they need a schema
144+ if username in users_to_create_schema :
145+ create_schema (username , username , database_name )
146+
147+ # Create instructure_dap schema for canvas user with them as owner
148+ if username == "canvas" :
149+ create_schema ("instructure_dap" , username , database_name )
150+
151+ # Assign privileges to canvas and instructure_dap schemas
152+ # Defaults to read-only if user is not set in user_roles dict
153+ user_role = get_user_role (username )
154+
155+ grant_usage_to_schema (username , "canvas" , database_name )
156+ assign_privileges (username , "canvas" , user_role , database_name )
157+
158+ grant_usage_to_schema (username , "instructure_dap" , database_name )
159+ assign_privileges (username , "instructure_dap" , user_role , database_name )
0 commit comments