cdk-rds-sql 7.3.1 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,338 +0,0 @@
1
- import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager"
2
- import { Client, ClientConfig } from "pg"
3
- import { GenericContainer, StartedTestContainer } from "testcontainers"
4
- import { handler } from "./handler"
5
- import {
6
- createRequest,
7
- updateRequest,
8
- deleteRequest,
9
- schemaExists,
10
- roleExists,
11
- databaseExists,
12
- databaseOwnerIs,
13
- rowCount,
14
- roleGrantedForSchema,
15
- } from "./util"
16
-
17
- jest.mock("@aws-sdk/client-secrets-manager")
18
- const SecretsManagerClientMock = SecretsManagerClient as jest.MockedClass<
19
- typeof SecretsManagerClient
20
- >
21
- SecretsManagerClientMock.prototype.send.mockImplementation(() => {
22
- return {
23
- SecretString: JSON.stringify({
24
- host: pgHost,
25
- port: pgPort,
26
- username: DB_MASTER_USERNAME,
27
- password: DB_MASTER_PASSWORD,
28
- dbname: DB_DEFAULT_DB,
29
- engine: "postgres",
30
- dbClusterIdentifier: "dummy",
31
- }),
32
- }
33
- })
34
-
35
- const DB_PORT = 5432
36
- const DB_MASTER_USERNAME = "pgroot"
37
- const DB_MASTER_PASSWORD = "masterpwd"
38
- const DB_DEFAULT_DB = "dummy"
39
-
40
- let pgContainer: StartedTestContainer
41
- let pgHost: string
42
- let pgPort: number
43
-
44
- beforeAll(async () => {
45
- process.env.LOGGER = "true"
46
- process.env.SSL = "false"
47
- process.env.CONNECTION_TIMEOUT = "5000"
48
- pgContainer = await new GenericContainer("postgres:15")
49
- .withExposedPorts(DB_PORT)
50
- .withEnvironment({
51
- POSTGRES_USER: DB_MASTER_USERNAME,
52
- POSTGRES_PASSWORD: DB_MASTER_PASSWORD,
53
- POSTGRES_DB: DB_DEFAULT_DB,
54
- })
55
- .start()
56
- pgHost = pgContainer.getHost()
57
- pgPort = pgContainer.getMappedPort(DB_PORT)
58
- }, 60000)
59
-
60
- afterAll(async () => {
61
- if (pgContainer) {
62
- await pgContainer.stop()
63
- }
64
- })
65
-
66
- beforeEach(async () => {
67
- jest.clearAllMocks()
68
-
69
- // Clean up databases, schemas, and roles created by tests
70
- const client = new Client({
71
- host: pgHost,
72
- port: pgPort,
73
- database: DB_DEFAULT_DB,
74
- user: DB_MASTER_USERNAME,
75
- password: DB_MASTER_PASSWORD,
76
- })
77
-
78
- await client.connect()
79
-
80
- try {
81
- // Drop all databases except system ones and default
82
- const databases = await client.query(
83
- "SELECT datname FROM pg_database WHERE datistemplate = false AND datname != $1",
84
- [DB_DEFAULT_DB]
85
- )
86
-
87
- for (const db of databases.rows) {
88
- await client.query(`DROP DATABASE IF EXISTS "${db.datname}"`)
89
- }
90
-
91
- // Drop all schemas except system ones
92
- const schemas = await client.query(
93
- "SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('information_schema', 'pg_catalog', 'pg_toast', 'public')"
94
- )
95
-
96
- for (const schema of schemas.rows) {
97
- await client.query(`DROP SCHEMA IF EXISTS "${schema.schema_name}" CASCADE`)
98
- }
99
-
100
- // Drop all roles except system ones
101
- const roles = await client.query(
102
- "SELECT rolname FROM pg_roles WHERE rolname NOT LIKE 'pg_%' AND rolname != $1",
103
- [DB_MASTER_USERNAME]
104
- )
105
-
106
- for (const role of roles.rows) {
107
- await client.query(`DROP ROLE IF EXISTS "${role.rolname}"`)
108
- }
109
- } finally {
110
- await client.end()
111
- }
112
- })
113
-
114
- //jest.setTimeout(ms("15s"))
115
-
116
- test("schema", async () => {
117
- const oldSchemaName = "test"
118
- const newSchemaName = "test2"
119
- const create = createRequest("schema", oldSchemaName)
120
- await handler(create)
121
- expect(SecretsManagerClientMock.prototype.send).toHaveBeenCalledTimes(1)
122
-
123
- const client = await newClient()
124
- try {
125
- expect(await schemaExists(client, oldSchemaName)).toEqual(true)
126
- const update = updateRequest("schema", oldSchemaName, newSchemaName)
127
- await handler(update)
128
- expect(await schemaExists(client, oldSchemaName)).toEqual(false)
129
- expect(await schemaExists(client, newSchemaName)).toEqual(true)
130
-
131
- // CloudFormation will send a delete afterward, so test that too
132
- const remove = deleteRequest("schema", newSchemaName)
133
- await handler(remove)
134
- expect(await schemaExists(client, newSchemaName)).toEqual(false)
135
-
136
- // create role for testing
137
- const roleName = "schematest"
138
- const createRole = createRequest("role", roleName, {
139
- PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy",
140
- DatabaseName: "postgres",
141
- })
142
- await handler(createRole)
143
-
144
- const createWithRole = createRequest("schema", oldSchemaName, {
145
- RoleName: roleName,
146
- })
147
- await handler(createWithRole)
148
- expect(await roleGrantedForSchema(client, oldSchemaName, roleName)).toEqual(true)
149
- const updateWithRole = updateRequest("schema", oldSchemaName, newSchemaName, {
150
- RoleName: roleName,
151
- })
152
- await handler(updateWithRole)
153
- expect(await roleGrantedForSchema(client, oldSchemaName, roleName)).toEqual(false)
154
- expect(await roleGrantedForSchema(client, newSchemaName, roleName)).toEqual(true)
155
- const removeWithRole = deleteRequest("schema", newSchemaName, {
156
- RoleName: roleName,
157
- })
158
- await handler(removeWithRole)
159
- expect(await roleGrantedForSchema(client, newSchemaName, roleName)).toEqual(false)
160
-
161
- const removeRole = deleteRequest("role", roleName, {
162
- DatabaseName: "postgres",
163
- })
164
- await handler(removeRole)
165
- } finally {
166
- await client.end()
167
- }
168
- })
169
-
170
- test("role with existing database", async () => {
171
- const oldRoleName = "example"
172
- const newRoleName = "example2"
173
- const create = createRequest("role", oldRoleName, {
174
- PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy",
175
- DatabaseName: "postgres",
176
- })
177
- await handler(create)
178
- expect(SecretsManagerClientMock.prototype.send).toHaveBeenCalledTimes(2)
179
- const client = await newClient()
180
- try {
181
- expect(await roleExists(client, oldRoleName)).toEqual(true)
182
-
183
- // Attempt to connect as this role
184
- const client2 = await newClient({
185
- user: oldRoleName,
186
- })
187
- await client2.end()
188
-
189
- const update = updateRequest("role", oldRoleName, newRoleName, {
190
- PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy",
191
- DatabaseName: "postgres",
192
- })
193
- await handler(update)
194
- expect(await roleExists(client, oldRoleName)).toEqual(false)
195
- expect(await roleExists(client, newRoleName)).toEqual(true)
196
-
197
- // CloudFormation will send a delete afterward as we change the
198
- // physical id, so test that too
199
- const remove = deleteRequest("role", oldRoleName, {
200
- DatabaseName: "postgres",
201
- })
202
- await handler(remove)
203
- expect(await roleExists(client, oldRoleName)).toEqual(false)
204
- } finally {
205
- await client.end()
206
- }
207
- })
208
-
209
- test("role without database", async () => {
210
- const roleName = "example"
211
- const create = createRequest("role", roleName, {
212
- PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy",
213
- })
214
- await handler(create)
215
- expect(SecretsManagerClientMock.prototype.send).toHaveBeenCalledTimes(2)
216
- const client = await newClient()
217
- try {
218
- expect(await roleExists(client, roleName)).toEqual(true)
219
- } finally {
220
- await client.end()
221
- }
222
- })
223
-
224
- test("change role password", async () => {
225
- const roleName = "example"
226
- const create = createRequest("role", roleName, {
227
- PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy",
228
- })
229
- await handler(create)
230
- expect(SecretsManagerClientMock.prototype.send).toHaveBeenCalledTimes(2)
231
- const client = await newClient()
232
- try {
233
- expect(await roleExists(client, roleName)).toEqual(true)
234
- const update = updateRequest("role", roleName, roleName, {
235
- PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy",
236
- })
237
- await handler(update)
238
- } finally {
239
- await client.end()
240
- }
241
- })
242
-
243
- test("database", async () => {
244
- const oldDatabaseName = "mydb"
245
- const newDatabaseName = "mydb2"
246
- const create = createRequest("database", oldDatabaseName)
247
- await handler(create)
248
- expect(SecretsManagerClientMock.prototype.send).toHaveBeenCalledTimes(1)
249
- const client = await newClient()
250
- try {
251
- expect(await databaseExists(client, oldDatabaseName)).toEqual(true)
252
- expect(await databaseOwnerIs(client, oldDatabaseName, DB_MASTER_USERNAME)).toEqual(
253
- true
254
- )
255
- const update = updateRequest("database", oldDatabaseName, newDatabaseName)
256
- await handler(update)
257
- expect(await databaseExists(client, oldDatabaseName)).toEqual(false)
258
- expect(await databaseExists(client, newDatabaseName)).toEqual(true)
259
-
260
- // CloudFormation will send a delete afterward, so test that too
261
- const remove = deleteRequest("database", oldDatabaseName)
262
- await handler(remove)
263
- expect(await databaseExists(client, oldDatabaseName)).toEqual(false)
264
- } finally {
265
- await client.end()
266
- }
267
- })
268
-
269
- test("database with owner", async () => {
270
- const databaseName = "mydb"
271
- const roleName = "example"
272
- const create_role = createRequest("role", roleName, {
273
- PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy",
274
- DatabaseName: "mydb", // database does not exist yet
275
- })
276
- await handler(create_role)
277
- const create_db = createRequest("database", databaseName, { Owner: "example" })
278
- await handler(create_db)
279
- const client = await newClient()
280
- try {
281
- expect(await databaseExists(client, databaseName)).toEqual(true)
282
- expect(await databaseOwnerIs(client, databaseName, roleName)).toEqual(true)
283
- const create_table = createRequest("sql", "", {
284
- DatabaseName: databaseName,
285
- Statement: "create table t(i int)",
286
- })
287
- await handler(create_table)
288
-
289
- // Verify we can login as owner
290
- {
291
- const client2 = await newClient({
292
- user: roleName,
293
- database: databaseName,
294
- })
295
- try {
296
- expect(await rowCount(client2, "t")).toEqual(0)
297
- } finally {
298
- await client2.end()
299
- }
300
- }
301
-
302
- const oldDatabaseName = "mydb"
303
- const newDatabaseName = "mydb2"
304
- const update = updateRequest("database", oldDatabaseName, newDatabaseName, {
305
- Owner: "example",
306
- })
307
- await handler(update)
308
- expect(await databaseExists(client, oldDatabaseName)).toEqual(false)
309
- expect(await databaseExists(client, newDatabaseName)).toEqual(true)
310
-
311
- // Verify we can login as owner against renamed database
312
- {
313
- const client2 = await newClient({
314
- user: roleName,
315
- database: newDatabaseName,
316
- })
317
- try {
318
- expect(await rowCount(client2, "t")).toEqual(0)
319
- } finally {
320
- await client2.end()
321
- }
322
- }
323
- } finally {
324
- await client.end()
325
- }
326
- })
327
-
328
- const newClient = async (config?: ClientConfig): Promise<Client> => {
329
- const client = new Client({
330
- host: pgHost,
331
- port: pgPort,
332
- database: config && config.database ? config.database : DB_DEFAULT_DB,
333
- user: DB_MASTER_USERNAME,
334
- password: DB_MASTER_PASSWORD,
335
- })
336
- await client.connect()
337
- return client
338
- }
@@ -1,164 +0,0 @@
1
- import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager"
2
- import { MysqlEngine } from "./engine.mysql"
3
- import { PostgresqlEngine } from "./engine.postgresql"
4
- import { handler } from "./handler"
5
- import { createRequest } from "./util"
6
-
7
- // Mock the secrets manager client
8
- jest.mock("@aws-sdk/client-secrets-manager")
9
- const SecretsManagerClientMock = SecretsManagerClient as jest.MockedClass<
10
- typeof SecretsManagerClient
11
- >
12
-
13
- // Mock the database engines
14
- jest.mock("./engine.mysql")
15
- jest.mock("./engine.postgresql")
16
-
17
- const MockedMysqlEngine = MysqlEngine as jest.MockedClass<typeof MysqlEngine>
18
- const MockedPostgresqlEngine = PostgresqlEngine as jest.MockedClass<
19
- typeof PostgresqlEngine
20
- >
21
-
22
- describe("Handler CloudFormation Property Conversion", () => {
23
- let mockMysqlEngine: jest.Mocked<MysqlEngine>
24
- let mockPostgresqlEngine: jest.Mocked<PostgresqlEngine>
25
-
26
- beforeEach(() => {
27
- jest.clearAllMocks()
28
-
29
- // Mock the engine instances
30
- mockMysqlEngine = {
31
- createRole: jest.fn(),
32
- executeSQL: jest.fn(),
33
- } as any
34
-
35
- mockPostgresqlEngine = {
36
- createRole: jest.fn(),
37
- executeSQL: jest.fn(),
38
- } as any
39
-
40
- MockedMysqlEngine.mockImplementation(() => mockMysqlEngine)
41
- MockedPostgresqlEngine.mockImplementation(() => mockPostgresqlEngine)
42
-
43
- // Mock secrets manager to return MySQL engine
44
- SecretsManagerClientMock.prototype.send.mockImplementation(() => {
45
- return Promise.resolve({
46
- SecretString: JSON.stringify({
47
- host: "localhost",
48
- port: 3306,
49
- username: "root",
50
- password: "password",
51
- dbname: "test",
52
- engine: "mysql",
53
- }),
54
- })
55
- })
56
- })
57
-
58
- describe("EnableIamAuth property conversion", () => {
59
- it("should convert string 'false' to boolean false", async () => {
60
- mockMysqlEngine.createRole.mockResolvedValue(["CREATE USER test"])
61
- mockMysqlEngine.executeSQL.mockResolvedValue({})
62
-
63
- // Create a request with EnableIamAuth as string 'false' (simulating CloudFormation)
64
- const request = createRequest("role", "testuser", {
65
- EnableIamAuth: "false",
66
- PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy",
67
- })
68
-
69
- await handler(request)
70
-
71
- // Verify that createRole was called with boolean false, not string 'false'
72
- expect(mockMysqlEngine.createRole).toHaveBeenCalledWith(
73
- "testuser",
74
- expect.objectContaining({
75
- EnableIamAuth: false, // Should be converted to boolean false
76
- PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy",
77
- })
78
- )
79
- })
80
-
81
- it("should convert string 'true' to boolean true", async () => {
82
- mockMysqlEngine.createRole.mockResolvedValue(["CREATE USER test"])
83
- mockMysqlEngine.executeSQL.mockResolvedValue({})
84
-
85
- // Create a request with EnableIamAuth as string 'true' (simulating CloudFormation)
86
- const request = createRequest("role", "testuser", {
87
- EnableIamAuth: "true", // CloudFormation passes boolean as string
88
- })
89
-
90
- await handler(request)
91
-
92
- // Verify that createRole was called with boolean true, not string 'true'
93
- expect(mockMysqlEngine.createRole).toHaveBeenCalledWith(
94
- "testuser",
95
- expect.objectContaining({
96
- EnableIamAuth: true, // Should be converted to boolean true
97
- })
98
- )
99
- })
100
-
101
- it("should handle number 0 correctly", async () => {
102
- mockMysqlEngine.createRole.mockResolvedValue(["CREATE USER test"])
103
- mockMysqlEngine.executeSQL.mockResolvedValue({})
104
-
105
- // Create a request with EnableIamAuth as actual boolean false
106
- const request = createRequest("role", "testuser", {
107
- EnableIamAuth: "0",
108
- PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy",
109
- })
110
-
111
- await handler(request)
112
-
113
- // Verify that createRole was called with boolean false
114
- expect(mockMysqlEngine.createRole).toHaveBeenCalledWith(
115
- "testuser",
116
- expect.objectContaining({
117
- EnableIamAuth: false,
118
- PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy",
119
- })
120
- )
121
- })
122
-
123
- it("should handle number 1 correctly", async () => {
124
- mockMysqlEngine.createRole.mockResolvedValue(["CREATE USER test"])
125
- mockMysqlEngine.executeSQL.mockResolvedValue({})
126
-
127
- // Create a request with EnableIamAuth as actual boolean true
128
- const request = createRequest("role", "testuser", {
129
- EnableIamAuth: "1",
130
- })
131
-
132
- await handler(request)
133
-
134
- // Verify that createRole was called with boolean true
135
- expect(mockMysqlEngine.createRole).toHaveBeenCalledWith(
136
- "testuser",
137
- expect.objectContaining({
138
- EnableIamAuth: true,
139
- })
140
- )
141
- })
142
-
143
- it("should handle undefined EnableIamAuth correctly", async () => {
144
- mockMysqlEngine.createRole.mockResolvedValue(["CREATE USER test"])
145
- mockMysqlEngine.executeSQL.mockResolvedValue({})
146
-
147
- // Create a request without EnableIamAuth property
148
- const request = createRequest("role", "testuser", {
149
- PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy",
150
- })
151
-
152
- await handler(request)
153
-
154
- // Verify that createRole was called without EnableIamAuth
155
- expect(mockMysqlEngine.createRole).toHaveBeenCalledWith(
156
- "testuser",
157
- expect.objectContaining({
158
- PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy",
159
- EnableIamAuth: false,
160
- })
161
- )
162
- })
163
- })
164
- })