Skip to content

Commit 2ac70c0

Browse files
Add optional read only user
1 parent 908fa86 commit 2ac70c0

File tree

1 file changed

+51
-10
lines changed

1 file changed

+51
-10
lines changed

cdk_bootstrapped_db/constructs.py

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
class BootstrappedDb(Construct):
2020
"""
2121
Given an RDS database, connect to DB and create a database, user, and
22-
password
22+
password, optionally a read-only user if `read_only_username` is provided.
2323
"""
2424

2525
def __init__(
@@ -32,6 +32,7 @@ def __init__(
3232
secrets_prefix: str,
3333
handler: Union[aws_lambda.Function, aws_lambda.SingletonFunction],
3434
db_engine: Optional[str] = None,
35+
read_only_username: Optional[str] = None,
3536
) -> None:
3637
super().__init__(scope, id)
3738

@@ -44,6 +45,7 @@ def __init__(
4445
" you must provide a value for `db_engine` explicitly."
4546
)
4647

48+
# Create secret for the main user
4749
self.secret = secretsmanager.Secret(
4850
self,
4951
id,
@@ -66,27 +68,66 @@ def __init__(
6668
description=f"Deployed by {Stack.of(self).stack_name}",
6769
)
6870

71+
# Optionally create a secret for the read-only user
72+
self.read_only_secret = None
73+
if read_only_username:
74+
self.read_only_secret = secretsmanager.Secret(
75+
self,
76+
f"{id}-readonly-secret",
77+
secret_name=os.path.join(
78+
secrets_prefix,
79+
f"{id}_readonly".replace(" ", "_"),
80+
self.node.id[-8:],
81+
),
82+
generate_secret_string=secretsmanager.SecretStringGenerator(
83+
secret_string_template=json.dumps(
84+
{
85+
"dbname": new_dbname,
86+
"engine": db_engine if db_engine else db.engine.engine_type, # type: ignore
87+
"port": db.instance_endpoint.port,
88+
"host": db.instance_endpoint.hostname,
89+
"username": read_only_username,
90+
},
91+
),
92+
generate_string_key="password",
93+
exclude_punctuation=True,
94+
),
95+
description=f"Read-only user secret deployed by {Stack.of(self).stack_name}",
96+
)
97+
6998
self.provider = custom_resources.Provider(
7099
scope, "BootstrapProvider", on_event_handler=handler
71100
)
72101

102+
# Prepare the properties for the custom resource
103+
resource_properties = {
104+
"conn_secret_arn": db.secret.secret_arn,
105+
"new_user_secret_arn": self.secret.secret_arn,
106+
"version": handler.current_version.version,
107+
}
108+
109+
# Optionally include the read-only secret ARN
110+
if self.read_only_secret:
111+
resource_properties["read_only_user_secret_arn"] = (
112+
self.read_only_secret.secret_arn
113+
)
114+
73115
self.resource = CustomResource(
74116
scope=scope,
75117
id="BootstrapHandlerResource",
76118
service_token=self.provider.service_token,
77-
properties={
78-
"conn_secret_arn": db.secret.secret_arn,
79-
"new_user_secret_arn": self.secret.secret_arn,
80-
"version": handler.current_version.version,
81-
},
119+
properties=resource_properties,
82120
)
83121

84-
# Allow lambda to...
85-
# read new user secret
122+
# Grant the Lambda handler permissions to read the main user secret
86123
self.secret.grant_read(handler)
87-
# read database secret
124+
125+
# If the read-only secret exists, grant the handler permissions to read it
126+
if self.read_only_secret:
127+
self.read_only_secret.grant_read(handler)
128+
129+
# Grant the Lambda handler permission to connect to the database
88130
db.secret.grant_read(handler)
89-
# connect to database
90131
db.connections.allow_from(handler, port_range=ec2.Port.tcp(5432))
91132

92133
def is_required_by(self, construct: Construct):

0 commit comments

Comments
 (0)