UNPKG

cdk-rds-sql

Version:

A CDK construct that allows creating roles and databases an on Aurora Serverless Postgresql cluster.

292 lines (266 loc) 9.12 kB
import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager" import { Client, ClientConfig } from "pg" import { GenericContainer, StartedTestContainer } from "testcontainers" import { createRequest, updateRequest, deleteRequest, schemaExists, roleExists, databaseExists, databaseOwnerIs, rowCount, roleGrantedForSchema, } from "./util" import { handler } from "./handler" jest.mock("@aws-sdk/client-secrets-manager") const SecretsManagerClientMock = SecretsManagerClient as jest.MockedClass< typeof SecretsManagerClient > SecretsManagerClientMock.prototype.send.mockImplementation(() => { return { SecretString: JSON.stringify({ host: pgHost, port: pgPort, username: DB_MASTER_USERNAME, password: DB_MASTER_PASSWORD, dbname: DB_DEFAULT_DB, engine: "postgres", dbClusterIdentifier: "dummy", }), } }) const DB_PORT = 5432 const DB_MASTER_USERNAME = "pgroot" const DB_MASTER_PASSWORD = "masterpwd" const DB_DEFAULT_DB = "dummy" let pgContainer: StartedTestContainer let pgHost: string let pgPort: number beforeEach(async () => { process.env.LOGGER = "true" process.env.SSL = "false" process.env.CONNECTION_TIMEOUT = "5000" pgContainer = await new GenericContainer("postgres:15") .withExposedPorts(DB_PORT) .withEnvironment({ POSTGRES_USER: DB_MASTER_USERNAME, POSTGRES_PASSWORD: DB_MASTER_PASSWORD, POSTGRES_DB: DB_DEFAULT_DB, }) .start() pgHost = pgContainer.getHost() pgPort = pgContainer.getMappedPort(DB_PORT) }, 30000) afterEach(async () => { jest.clearAllMocks() if (pgContainer) { await pgContainer.stop() } }) //jest.setTimeout(ms("15s")) test("schema", async () => { const oldSchemaName = "test" const newSchemaName = "test2" const create = createRequest("schema", oldSchemaName) await handler(create) expect(SecretsManagerClientMock).toHaveBeenCalledTimes(1) const client = await newClient() try { expect(await schemaExists(client, oldSchemaName)).toEqual(true) const update = updateRequest("schema", oldSchemaName, newSchemaName) await handler(update) expect(await schemaExists(client, oldSchemaName)).toEqual(false) expect(await schemaExists(client, newSchemaName)).toEqual(true) // CloudFormation will send a delete afterward, so test that too const remove = deleteRequest("schema", newSchemaName) await handler(remove) expect(await schemaExists(client, newSchemaName)).toEqual(false) // create role for testing const roleName = "schematest" const createRole = createRequest("role", roleName, { PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy", DatabaseName: "postgres", }) await handler(createRole) const createWithRole = createRequest("schema", oldSchemaName, { RoleName: roleName, }) await handler(createWithRole) expect(await roleGrantedForSchema(client, oldSchemaName, roleName)).toEqual(true) const updateWithRole = updateRequest("schema", oldSchemaName, newSchemaName, { RoleName: roleName, }) await handler(updateWithRole) expect(await roleGrantedForSchema(client, oldSchemaName, roleName)).toEqual(false) expect(await roleGrantedForSchema(client, newSchemaName, roleName)).toEqual(true) const removeWithRole = deleteRequest("schema", newSchemaName, { RoleName: roleName, }) await handler(removeWithRole) expect(await roleGrantedForSchema(client, newSchemaName, roleName)).toEqual(false) const removeRole = deleteRequest("role", roleName, { DatabaseName: "postgres", }) await handler(removeRole) } finally { await client.end() } }) test("role with existing database", async () => { const oldRoleName = "example" const newRoleName = "example2" const create = createRequest("role", oldRoleName, { PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy", DatabaseName: "postgres", }) await handler(create) expect(SecretsManagerClientMock).toHaveBeenCalledTimes(2) const client = await newClient() try { expect(await roleExists(client, oldRoleName)).toEqual(true) // Attempt to connect as this role const client2 = await newClient({ user: oldRoleName, }) await client2.end() const update = updateRequest("role", oldRoleName, newRoleName, { PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy", DatabaseName: "postgres", }) await handler(update) expect(await roleExists(client, oldRoleName)).toEqual(false) expect(await roleExists(client, newRoleName)).toEqual(true) // CloudFormation will send a delete afterward as we change the // physical id, so test that too const remove = deleteRequest("role", oldRoleName, { DatabaseName: "postgres", }) await handler(remove) expect(await roleExists(client, oldRoleName)).toEqual(false) } finally { await client.end() } }) test("role without database", async () => { const roleName = "example" const create = createRequest("role", roleName, { PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy", }) await handler(create) expect(SecretsManagerClientMock).toHaveBeenCalledTimes(2) const client = await newClient() try { expect(await roleExists(client, roleName)).toEqual(true) } finally { await client.end() } }) test("change role password", async () => { const roleName = "example" const create = createRequest("role", roleName, { PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy", }) await handler(create) expect(SecretsManagerClientMock).toHaveBeenCalledTimes(2) const client = await newClient() try { expect(await roleExists(client, roleName)).toEqual(true) const update = updateRequest("role", roleName, roleName, { PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy", }) await handler(update) } finally { await client.end() } }) test("database", async () => { const oldDatabaseName = "mydb" const newDatabaseName = "mydb2" const create = createRequest("database", oldDatabaseName) await handler(create) expect(SecretsManagerClientMock).toHaveBeenCalledTimes(1) const client = await newClient() try { expect(await databaseExists(client, oldDatabaseName)).toEqual(true) expect(await databaseOwnerIs(client, oldDatabaseName, DB_MASTER_USERNAME)).toEqual( true ) const update = updateRequest("database", oldDatabaseName, newDatabaseName) await handler(update) expect(await databaseExists(client, oldDatabaseName)).toEqual(false) expect(await databaseExists(client, newDatabaseName)).toEqual(true) // CloudFormation will send a delete afterward, so test that too const remove = deleteRequest("database", oldDatabaseName) await handler(remove) expect(await databaseExists(client, oldDatabaseName)).toEqual(false) } finally { await client.end() } }) test("database with owner", async () => { const databaseName = "mydb" const roleName = "example" const create_role = createRequest("role", roleName, { PasswordArn: "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy", DatabaseName: "mydb", // database does not exist yet }) await handler(create_role) const create_db = createRequest("database", databaseName, { Owner: "example" }) await handler(create_db) const client = await newClient() try { expect(await databaseExists(client, databaseName)).toEqual(true) expect(await databaseOwnerIs(client, databaseName, roleName)).toEqual(true) const create_table = createRequest("sql", "", { DatabaseName: databaseName, Statement: "create table t(i int)", }) await handler(create_table) // Verify we can login as owner { const client2 = await newClient({ user: roleName, database: databaseName, }) try { expect(await rowCount(client2, "t")).toEqual(0) } finally { await client2.end() } } const oldDatabaseName = "mydb" const newDatabaseName = "mydb2" const update = updateRequest("database", oldDatabaseName, newDatabaseName, { Owner: "example", }) await handler(update) expect(await databaseExists(client, oldDatabaseName)).toEqual(false) expect(await databaseExists(client, newDatabaseName)).toEqual(true) // Verify we can login as owner against renamed database { const client2 = await newClient({ user: roleName, database: newDatabaseName, }) try { expect(await rowCount(client2, "t")).toEqual(0) } finally { await client2.end() } } } finally { await client.end() } }) const newClient = async (config?: ClientConfig): Promise<Client> => { const client = new Client({ host: pgHost, port: pgPort, database: config && config.database ? config.database : DB_DEFAULT_DB, user: DB_MASTER_USERNAME, password: DB_MASTER_PASSWORD, }) await client.connect() return client }