UNPKG

cdk-rds-sql

Version:

A CDK construct that allows creating roles or users and databases an on Aurora Serverless Postgresql or Mysql/MariaDB cluster.

187 lines (172 loc) 5.75 kB
import { CloudFormationCustomResourceCreateEvent, CloudFormationCustomResourceUpdateEvent, CloudFormationCustomResourceDeleteEvent, } from "aws-lambda" import { Client } from "pg" import { ResourceProperties } from "./types" // Helper interface to simplify test request creation export interface ResourcePropertiesInput { readonly SecretArn?: string readonly PasswordArn?: string readonly DatabaseName?: string readonly Owner?: string readonly Statement?: string readonly Rollback?: string readonly RoleName?: string readonly ParameterName?: string [key: string]: any } /** * Helpers to create CloudFormation requests. */ export const createRequest = ( resource: string, resourceId: string, props?: Partial<ResourceProperties> ): CloudFormationCustomResourceCreateEvent<ResourceProperties> => { return { ServiceToken: "", ResponseURL: "", StackId: "", RequestId: "", LogicalResourceId: "", ResourceType: "", ResourceProperties: { ServiceToken: "", Resource: resource as any, // Needed for backward compatibility with tests ResourceId: resourceId, SecretArn: props?.SecretArn || "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy", ...props, }, RequestType: "Create", } } export const updateRequest = ( resource: string, oldResourceId: string, newResourceId: string, props?: ResourcePropertiesInput ): CloudFormationCustomResourceUpdateEvent<ResourceProperties> => { return { ServiceToken: "", ResponseURL: "", StackId: "", RequestId: "", LogicalResourceId: "", ResourceType: "", ResourceProperties: { ServiceToken: "", Resource: resource as any, // Needed for backward compatibility with tests ResourceId: newResourceId, SecretArn: props?.SecretArn || "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy", ...props, } as unknown as ResourceProperties, RequestType: "Update", PhysicalResourceId: oldResourceId, OldResourceProperties: { ServiceToken: "", Resource: resource as any, ResourceId: oldResourceId, SecretArn: props?.SecretArn || "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy", } as unknown as ResourceProperties, } } export const deleteRequest = ( resource: string, resourceId: string, props?: ResourcePropertiesInput ): CloudFormationCustomResourceDeleteEvent<ResourceProperties> => { return { ServiceToken: "", ResponseURL: "", StackId: "", RequestId: "", LogicalResourceId: "", ResourceType: "", ResourceProperties: { ServiceToken: "", Resource: resource as any, // Needed for backward compatibility with tests ResourceId: resourceId, SecretArn: props?.SecretArn || "arn:aws:secretsmanager:us-east-1:123456789:secret:dummy", ...props, } as unknown as ResourceProperties, RequestType: "Delete", PhysicalResourceId: resourceId, } } /** * Database helpers. */ export const schemaExists = async (client: Client, schema: string): Promise<boolean> => { const schemas = await getSchemas(client) return schemas.find((s) => s === schema) !== undefined } export const roleGrantedForSchema = async ( client: Client, schema: string, role: string ): Promise<boolean> => { const sql = `select nspname as schema_name, r.rolname as role_name,\ pg_catalog.has_schema_privilege(r.rolname, nspname, 'CREATE') as create_grant,\ pg_catalog.has_schema_privilege(r.rolname, nspname, 'USAGE') as usage_grant\ from pg_namespace pn,pg_catalog.pg_roles r \ where array_to_string(nspacl,',') like '%'||r.rolname||'%' and nspowner > 1 \ and nspname = '${schema}' and r.rolname = '${role}'` const { rows } = await client.query(sql) return ( rows.length === 1 && rows[0].create_grant === true && rows[0].usage_grant === true ) } const getSchemas = async (client: Client): Promise<string[]> => { const { rows } = await client.query( "select schema_name from information_schema.schemata" ) return rows.map((r) => r.schema_name) } export const roleExists = async (client: Client, role: string): Promise<boolean> => { const roles = await getRoles(client) return roles.find((r) => r === role) !== undefined } const getRoles = async (client: Client): Promise<string[]> => { const { rows } = await client.query("select rolname from pg_roles") return rows.map((r) => r.rolname) } export const databaseExists = async ( client: Client, database: string ): Promise<boolean> => { const databases = await getDatabases(client) return databases.find((r) => r === database) !== undefined } const getDatabases = async (client: Client): Promise<string[]> => { const { rows } = await client.query( "select datname from pg_database where datistemplate = false" ) return rows.map((r) => r.datname) } export const databaseOwnerIs = async ( client: Client, database: string, user_name: string ): Promise<boolean> => { const databases = await getDatabasesWithOwner(client, user_name) return databases.find((r) => r === database) !== undefined } const getDatabasesWithOwner = async ( client: Client, user_name: string ): Promise<string[]> => { const { rows } = await client.query( `select datname from pg_database where datistemplate = false and pg_catalog.pg_get_userbyid(datdba) = '${user_name}'` ) return rows.map((r) => r.datname) } export const rowCount = async (client: Client, table_name: string): Promise<number> => { const { rows } = await client.query(`select count(*) from ${table_name}`) return parseInt(rows[0].count) }