From 5404d097da0910e34a334aa6a7ca9a537f00201c Mon Sep 17 00:00:00 2001 From: William Putra Intan <61998484+williamputraintan@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:31:53 +1100 Subject: [PATCH 01/10] init postgres-manager --- config/constants.ts | 18 +- lib/workload/orcabus-stateless-stack.ts | 44 +- lib/workload/stateful/database/component.ts | 13 + .../stateless/postgres_manager/.gitignore | 11 + .../stateless/postgres_manager/README.md | 95 ++ .../construct/postgresManager.ts | 127 ++ .../postgres_manager/deploy/stack.ts | 127 ++ .../function/alter-pg-db-owner.ts | 25 + .../postgres_manager/function/create-pg-db.ts | 30 + .../function/create-pg-iam-role.ts | 31 + .../function/create-pg-login-role.ts | 89 ++ .../postgres_manager/function/utils.ts | 100 ++ .../stateless/postgres_manager/package.json | 15 + .../stateless/postgres_manager/yarn.lock | 1408 +++++++++++++++++ 14 files changed, 2117 insertions(+), 16 deletions(-) create mode 100644 lib/workload/stateless/postgres_manager/.gitignore create mode 100644 lib/workload/stateless/postgres_manager/README.md create mode 100644 lib/workload/stateless/postgres_manager/construct/postgresManager.ts create mode 100644 lib/workload/stateless/postgres_manager/deploy/stack.ts create mode 100644 lib/workload/stateless/postgres_manager/function/alter-pg-db-owner.ts create mode 100644 lib/workload/stateless/postgres_manager/function/create-pg-db.ts create mode 100644 lib/workload/stateless/postgres_manager/function/create-pg-iam-role.ts create mode 100644 lib/workload/stateless/postgres_manager/function/create-pg-login-role.ts create mode 100644 lib/workload/stateless/postgres_manager/function/utils.ts create mode 100644 lib/workload/stateless/postgres_manager/package.json create mode 100644 lib/workload/stateless/postgres_manager/yarn.lock diff --git a/config/constants.ts b/config/constants.ts index e8c8d0a39..1771a0c6f 100644 --- a/config/constants.ts +++ b/config/constants.ts @@ -6,10 +6,13 @@ import { } from '../lib/workload/orcabus-stateless-stack'; import { Duration, aws_lambda, RemovalPolicy } from 'aws-cdk-lib'; import { EventSourceProps } from '../lib/workload/stateful/event_source/component'; +import { DbAuthType } from '../lib/workload/stateless/postgres_manager/function/utils'; const regName = 'OrcaBusSchemaRegistry'; const eventBusName = 'OrcaBusMain'; const lambdaSecurityGroupName = 'OrcaBusLambdaSecurityGroup'; +const dbClusterIdentifier = 'orcabus-db'; +const dbClusterResourceIdParameterName = '/orcabus/db-cluster-resource-id'; // Note, this should not end with a hyphen and 6 characters, otherwise secrets manager won't be // able to find the secret using a partial ARN. @@ -27,7 +30,7 @@ const orcaBusStatefulConfig = { archiveRetention: 365, }, databaseProps: { - clusterIdentifier: 'orcabus-db', + clusterIdentifier: dbClusterIdentifier, defaultDatabaseName: 'orcabus', version: AuroraPostgresEngineVersion.VER_15_4, parameterGroupName: 'default.aurora-postgresql15', @@ -37,6 +40,7 @@ const orcaBusStatefulConfig = { monitoring: { cloudwatchLogsExports: ['orcabus-postgresql'], }, + clusterResourceIdParameterName: dbClusterResourceIdParameterName, }, securityGroupProps: { securityGroupName: lambdaSecurityGroupName, @@ -67,6 +71,18 @@ const orcaBusStatelessConfig = { lambdaRuntimePythonVersion: aws_lambda.Runtime.PYTHON_3_10, bclConvertFunctionName: 'orcabus_bcl_convert', rdsMasterSecretName: rdsMasterSecretName, + postgresManagerConfig: { + masterSecretName: rdsMasterSecretName, + dbClusterIdentifier: dbClusterIdentifier, + clusterResourceIdParameterName: dbClusterResourceIdParameterName, + microserviceDbConfig: [ + { + name: 'metadata_manager', + authType: DbAuthType.USERNAME_PASSWORD, + }, + { name: 'filemanager', authType: DbAuthType.RDS_IAM }, + ], + }, }; const eventSourceConfig: EventSourceProps = { diff --git a/lib/workload/orcabus-stateless-stack.ts b/lib/workload/orcabus-stateless-stack.ts index db61af5b2..f13b59c8a 100644 --- a/lib/workload/orcabus-stateless-stack.ts +++ b/lib/workload/orcabus-stateless-stack.ts @@ -3,10 +3,14 @@ import { Arn, aws_lambda } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { getVpc } from './stateful/vpc/component'; import { MultiSchemaConstructProps } from './stateless/schema/component'; -import { IVpc, SecurityGroup } from 'aws-cdk-lib/aws-ec2'; +import { IVpc, ISecurityGroup, SecurityGroup } from 'aws-cdk-lib/aws-ec2'; import { Filemanager } from './stateless/filemanager/deploy/lib/filemanager'; import { Queue } from 'aws-cdk-lib/aws-sqs'; import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; +import { + PostgresManager, + PostgresManagerConfig, +} from './stateless/postgres_manager/construct/postgresManager'; export interface OrcaBusStatelessConfig { multiSchemaConstructProps: MultiSchemaConstructProps; @@ -15,6 +19,7 @@ export interface OrcaBusStatelessConfig { lambdaRuntimePythonVersion: aws_lambda.Runtime; bclConvertFunctionName: string; rdsMasterSecretName: string; + postgresManagerConfig: PostgresManagerConfig; filemanagerDependencies?: FilemanagerDependencies; } @@ -35,6 +40,7 @@ export interface FilemanagerDependencies { export class OrcaBusStatelessStack extends cdk.Stack { private vpc: IVpc; + private lambdaSecurityGroup: ISecurityGroup; constructor(scope: Construct, id: string, props: cdk.StackProps & OrcaBusStatelessConfig) { super(scope, id, props); @@ -42,14 +48,12 @@ export class OrcaBusStatelessStack extends cdk.Stack { this.vpc = getVpc(this); - // const securityGroups = [ - // aws_ec2.SecurityGroup.fromLookupByName( - // this, - // 'LambdaSecurityGroup', - // props.lambdaSecurityGroupName, - // vpc - // ), - // ]; + this.lambdaSecurityGroup = SecurityGroup.fromLookupByName( + this, + 'OrcaBusLambdaSecurityGroup', + props.lambdaSecurityGroupName, + this.vpc + ); // const mainBus = EventBus.fromEventBusName(this, 'OrcaBusMain', props.eventBusName); @@ -60,12 +64,14 @@ export class OrcaBusStatelessStack extends cdk.Stack { // hook microservice construct components here this.createSequenceRunManager(); - if (props.filemanagerDependencies) { - this.createFilemanager({ - ...props.filemanagerDependencies, - lambdaSecurityGroupName: props.lambdaSecurityGroupName, - }); - } + this.createPostgresManager(props.postgresManagerConfig); + + // if (props.filemanagerDependencies) { + // this.createFilemanager({ + // ...props.filemanagerDependencies, + // lambdaSecurityGroupName: props.lambdaSecurityGroupName, + // }); + // } } private createSequenceRunManager() { @@ -73,6 +79,14 @@ export class OrcaBusStatelessStack extends cdk.Stack { // However, the implementation is still incomplete... } + private createPostgresManager(config: PostgresManagerConfig) { + new PostgresManager(this, 'PostgresManager', { + ...config, + vpc: this.vpc, + lambdaSecurityGroup: this.lambdaSecurityGroup, + }); + } + private createFilemanager( dependencies: FilemanagerDependencies & { lambdaSecurityGroupName: string } ) { diff --git a/lib/workload/stateful/database/component.ts b/lib/workload/stateful/database/component.ts index 2052590e7..b3f737280 100644 --- a/lib/workload/stateful/database/component.ts +++ b/lib/workload/stateful/database/component.ts @@ -2,6 +2,7 @@ import { Construct } from 'constructs'; import { RemovalPolicy, Duration } from 'aws-cdk-lib'; import * as rds from 'aws-cdk-lib/aws-rds'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; import { SecurityGroup } from 'aws-cdk-lib/aws-ec2'; import { DatabaseCluster } from 'aws-cdk-lib/aws-rds'; @@ -75,6 +76,10 @@ export type ConfigurableDatabaseProps = MonitoringProps & { * The database removal policy. */ removalPolicy: RemovalPolicy; + /** + * The ssm parameter name to store the cluster resource id + */ + clusterResourceIdParameterName: string; }; /** @@ -148,5 +153,13 @@ export class Database extends Construct { enablePerformanceInsights: props.enablePerformanceInsights, }), }); + + // saving the cluster id to be used in stateless stack on rds-iam + // ref: https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.IAMPolicy.html + new ssm.StringParameter(this, 'DbClusterResourceIdSSM', { + stringValue: this.cluster.clusterResourceIdentifier, + description: 'cluster resource id at the orcabus rds cluster', + parameterName: props.clusterResourceIdParameterName, + }); } } diff --git a/lib/workload/stateless/postgres_manager/.gitignore b/lib/workload/stateless/postgres_manager/.gitignore new file mode 100644 index 000000000..d52967e16 --- /dev/null +++ b/lib/workload/stateless/postgres_manager/.gitignore @@ -0,0 +1,11 @@ +# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +out.txt +response.json diff --git a/lib/workload/stateless/postgres_manager/README.md b/lib/workload/stateless/postgres_manager/README.md new file mode 100644 index 000000000..25d19382b --- /dev/null +++ b/lib/workload/stateless/postgres_manager/README.md @@ -0,0 +1,95 @@ +# RDS Postgres Manager + +This will deploy lambdas that will connect to the RDS instance with the master credential. This microservice is +responsible for admin Postgres activity that requires superuser access. + +Before using this microservice you would need to configure this lambda by passing it as props from stateless config +(`./config/constants.ts`) at the postgresManagerConfig. + +The config should register the microservice name and the connection type to the RDS. The connection can +make use of `rds_iam` or the conventional `user-password` connection string. + +The microservice config should look as follows: + +```json + microserviceDbConfig: [ + { + name: 'metadata_manager', + authType: DbAuthType.USERNAME_PASSWORD, + }, + { name: 'filemanager', authType: DbAuthType.RDS_IAM }, + ] +``` + +The DbAuthType is defined at the [./function/utils.ts](./function/utils.ts) in this project and it is as follows: + +```ts +export enum DbAuthType { + RDS_IAM, + USERNAME_PASSWORD, +} +``` + +There are 4 lambdas in this stack: + +1. `orcabus-create-pg-db` + + This aimed to create a new database name for the microservice. The database name will be the same as the + microservice name. + + ```sh + aws lambda invoke \ + --function-name orcabus-create-pg-db \ + --payload '{ "microserviceName": "microservice_name" }' \ + --cli-binary-format raw-in-base64-out \ + response.json + ``` + +2. `orcabus-create-pg-login-role` + + Create a role with login credentials used for this microservice. + The name of the role would be the microservice name itself, and the credential will be saved into the secret + manager. The secret manager name is saved to `orcabus/microservice/${microserviceName}`. + + Note: this will only work if the DbAuthType is configured to `USERNAME_PASSWORD`. + + ```sh + aws lambda invoke \ + --function-name orcabus-create-pg-login-role \ + --payload '{ "microserviceName": "microservice_name" }' \ + --cli-binary-format raw-in-base64-out \ + response.json + ``` + +3. `orcabus-create-pg-iam-role` + + Create a new role and assign `rds_iam` role to this role to be able to connect over IAM database authentication. + + A new managed policy will be created and the policy name + will be `orcabus-rds-connect-${microservice_name}`. This could be attached to your compute role for access to the RDS and the token needed. Follow the documentation from AWS to connect to the RDS [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.Connecting.html). + + Note: this will only work if the microservice DbAuthType is configured to `RDS_IAM`. + + Ref: + [aws-rds-iam-postgres](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.DBAccounts.html#UsingWithRDS.IAMDBAuth.DBAccounts.PostgreSQL) + + ```sh + aws lambda invoke \ + --function-name orcabus-create-pg-iam-role \ + --payload '{ "microserviceName": "microservice_name" }' \ + --cli-binary-format raw-in-base64-out \ + response.json + ``` + +4. `orcabus-alter-pg-db-owner` + + Alter existing db to its respective role. The respective role will be the microservice user role created in either + lambda number 2 or 3. + + ```sh + aws lambda invoke \ + --function-name orcabus-alter-pg-db-owner \ + --payload '{ "microserviceName": "microservice_name" }' \ + --cli-binary-format raw-in-base64-out \ + response.json + ``` diff --git a/lib/workload/stateless/postgres_manager/construct/postgresManager.ts b/lib/workload/stateless/postgres_manager/construct/postgresManager.ts new file mode 100644 index 000000000..09eeb1030 --- /dev/null +++ b/lib/workload/stateless/postgres_manager/construct/postgresManager.ts @@ -0,0 +1,127 @@ +import { Duration } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as nodejs from 'aws-cdk-lib/aws-lambda-nodejs'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { DbAuthType, MicroserviceConfig } from '../function/utils'; + +export type PostgresManagerConfig = { + masterSecretName: string; + dbClusterIdentifier: string; + microserviceDbConfig: MicroserviceConfig; + clusterResourceIdParameterName: string; +}; + +export type PostgresManagerStackProps = PostgresManagerConfig & { + vpc: ec2.IVpc; + lambdaSecurityGroup: ec2.ISecurityGroup; +}; + +export class PostgresManager extends Construct { + constructor(scope: Construct, id: string, props: PostgresManagerStackProps) { + super(scope, id); + + const { dbClusterIdentifier, microserviceDbConfig } = props; + + const masterSecret = secretsmanager.Secret.fromSecretNameV2( + this, + 'RdsMasterSecret', + props.masterSecretName + ); + + const dbClusterResourceId = ssm.StringParameter.valueFromLookup( + this, + '/orcabus/db-cluster-resource-id' + ); + + // Let lambda access secret manager via aws managed lambda extension + // Ref: + // https://aws.amazon.com/blogs/compute/using-the-aws-parameter-and-secrets-lambda-extension-to-cache-parameters-and-secrets/ + const lambdaLayerGetSecretExtension = lambda.LayerVersion.fromLayerVersionArn( + this, + 'GetSecretExtensionLayer', + 'arn:aws:lambda:ap-southeast-2:665172237481:layer:AWS-Parameters-and-Secrets-Lambda-Extension-Arm64:11' + ); + + const rdsLambdaProps = { + timeout: Duration.minutes(5), + depsLockFilePath: __dirname + '/../yarn.lock', + handler: 'handler', + layers: [lambdaLayerGetSecretExtension], + runtime: lambda.Runtime.NODEJS_20_X, + architecture: lambda.Architecture.ARM_64, + environment: { + RDS_SECRET_MANAGER_NAME: masterSecret.secretName, + MICROSERVICE_CONFIG: JSON.stringify(props.microserviceDbConfig), + }, + vpc: props.vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [props.lambdaSecurityGroup], + }; + + // create new database lambda + const createPgDb = new nodejs.NodejsFunction(this, 'CreateDbPostgresLambda', { + ...rdsLambdaProps, + entry: __dirname + '/../function/create-pg-db.ts', + functionName: 'orcabus-create-pg-db', + }); + masterSecret.grantRead(createPgDb); + + // create role which has the rds-iam + const initiatePgRdsIam = new nodejs.NodejsFunction(this, 'CreateIamUserPostgresLambda', { + ...rdsLambdaProps, + entry: __dirname + '/../function/create-pg-iam-role.ts', + functionName: 'orcabus-create-pg-iam-role', + }); + masterSecret.grantRead(initiatePgRdsIam); + + // create iam-policy that could be assumed when using the rds-iam + for (const microservice of microserviceDbConfig) { + if (microservice.authType == DbAuthType.RDS_IAM) { + const iamPolicy = new iam.ManagedPolicy(this, `${microservice.name}RdsIamPolicy`, { + managedPolicyName: `orcabus-rds-connect-${microservice.name}`, + }); + + const dbCluster = rds.DatabaseCluster.fromDatabaseClusterAttributes( + this, + 'OrcabusDbCluster', + { + clusterIdentifier: dbClusterIdentifier, + clusterResourceIdentifier: dbClusterResourceId, + } + ); + + dbCluster.grantConnect(iamPolicy, microservice.name); + } + } + + // create role with username-password login + const createRolePgLambda = new nodejs.NodejsFunction(this, 'CreateUserPassPostgresLambda', { + ...rdsLambdaProps, + initialPolicy: [ + new iam.PolicyStatement({ + actions: ['secretsmanager:CreateSecret', 'secretsmanager:TagResource'], + effect: iam.Effect.ALLOW, + resources: ['arn:aws:secretsmanager:ap-southeast-2:*:secret:*'], + }), + ], + entry: __dirname + '/../function/create-pg-login-role.ts', + functionName: 'orcabus-create-pg-login-role', + }); + masterSecret.grantRead(createRolePgLambda); + + // alter db owner to its respective role + const alterDbPgOwnerLambda = new nodejs.NodejsFunction(this, 'AlterDbOwnerPostgresLambda', { + ...rdsLambdaProps, + entry: __dirname + '/../function/alter-pg-db-owner.ts', + functionName: 'orcabus-alter-pg-db-owner', + }); + masterSecret.grantRead(alterDbPgOwnerLambda); + } +} diff --git a/lib/workload/stateless/postgres_manager/deploy/stack.ts b/lib/workload/stateless/postgres_manager/deploy/stack.ts new file mode 100644 index 000000000..aebd4364a --- /dev/null +++ b/lib/workload/stateless/postgres_manager/deploy/stack.ts @@ -0,0 +1,127 @@ +import { Duration } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as nodejs from 'aws-cdk-lib/aws-lambda-nodejs'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { DbAuthType, MicroserviceConfig } from '../function/utils'; + +export type PostgresManagerStackConfig = { + masterSecretName: string; + dbClusterIdentifier: string; + microserviceDbConfig: MicroserviceConfig; + clusterResourceIdParameterName: string; +}; + +export type PostgresManagerStackProps = PostgresManagerStackConfig & { + vpc: ec2.IVpc; + lambdaSecurityGroup: ec2.ISecurityGroup; +}; + +export class PostgresManagerStack extends Construct { + constructor(scope: Construct, id: string, props: PostgresManagerStackProps) { + super(scope, id); + + const { dbClusterIdentifier, microserviceDbConfig } = props; + + const masterSecret = secretsmanager.Secret.fromSecretNameV2( + this, + 'RdsMasterSecret', + props.masterSecretName + ); + + const dbClusterResourceId = ssm.StringParameter.valueFromLookup( + this, + '/orcabus/db-cluster-resource-id' + ); + + // Let lambda access secret manager via aws managed lambda extension + // Ref: + // https://aws.amazon.com/blogs/compute/using-the-aws-parameter-and-secrets-lambda-extension-to-cache-parameters-and-secrets/ + const lambdaLayerGetSecretExtension = lambda.LayerVersion.fromLayerVersionArn( + this, + 'GetSecretExtensionLayer', + 'arn:aws:lambda:ap-southeast-2:665172237481:layer:AWS-Parameters-and-Secrets-Lambda-Extension-Arm64:11' + ); + + const rdsLambdaProps = { + timeout: Duration.minutes(5), + depsLockFilePath: __dirname + '/../yarn.lock', + handler: 'handler', + layers: [lambdaLayerGetSecretExtension], + runtime: lambda.Runtime.NODEJS_20_X, + architecture: lambda.Architecture.ARM_64, + environment: { + RDS_SECRET_MANAGER_NAME: masterSecret.secretName, + MICROSERVICE_CONFIG: JSON.stringify(props.microserviceDbConfig), + }, + vpc: props.vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [props.lambdaSecurityGroup], + }; + + // create new database lambda + const createPgDb = new nodejs.NodejsFunction(this, 'CreateDbPostgresLambda', { + ...rdsLambdaProps, + entry: __dirname + '/../function/create-pg-db.ts', + functionName: 'orcabus-create-pg-db', + }); + masterSecret.grantRead(createPgDb); + + // create role which has the rds-iam + const initiatePgRdsIam = new nodejs.NodejsFunction(this, 'CreateIamUserPostgresLambda', { + ...rdsLambdaProps, + entry: __dirname + '/../function/create-pg-iam-role.ts', + functionName: 'orcabus-create-pg-iam-role', + }); + masterSecret.grantRead(initiatePgRdsIam); + + // create iam-policy that could be assumed when using the rds-iam + for (const microservice of microserviceDbConfig) { + if (microservice.authType == DbAuthType.RDS_IAM) { + const iamPolicy = new iam.ManagedPolicy(this, `${microservice.name}RdsIamPolicy`, { + managedPolicyName: `orcabus-rds-connect-${microservice.name}`, + }); + + const dbCluster = rds.DatabaseCluster.fromDatabaseClusterAttributes( + this, + 'OrcabusDbCluster', + { + clusterIdentifier: dbClusterIdentifier, + clusterResourceIdentifier: dbClusterResourceId, + } + ); + + dbCluster.grantConnect(iamPolicy, microservice.name); + } + } + + // create role with username-password login + const createRolePgLambda = new nodejs.NodejsFunction(this, 'CreateUserPassPostgresLambda', { + ...rdsLambdaProps, + initialPolicy: [ + new iam.PolicyStatement({ + actions: ['secretsmanager:CreateSecret', 'secretsmanager:TagResource'], + effect: iam.Effect.ALLOW, + resources: ['arn:aws:secretsmanager:ap-southeast-2:*:secret:*'], + }), + ], + entry: __dirname + '/../function/create-pg-login-role.ts', + functionName: 'orcabus-create-pg-login-role', + }); + masterSecret.grantRead(createRolePgLambda); + + // alter db owner to its respective role + const alterDbPgOwnerLambda = new nodejs.NodejsFunction(this, 'AlterDbOwnerPassPostgresLambda', { + ...rdsLambdaProps, + entry: __dirname + '/../function/alter-pg-db-owner.ts', + functionName: 'orcabus-alter-pg-db-owner', + }); + masterSecret.grantRead(alterDbPgOwnerLambda); + } +} diff --git a/lib/workload/stateless/postgres_manager/function/alter-pg-db-owner.ts b/lib/workload/stateless/postgres_manager/function/alter-pg-db-owner.ts new file mode 100644 index 000000000..4d21b213a --- /dev/null +++ b/lib/workload/stateless/postgres_manager/function/alter-pg-db-owner.ts @@ -0,0 +1,25 @@ +import { Client } from 'pg'; +import { + EventType, + getMicroserviceConfig, + getMicroserviceName, + executeSqlWithLog, + getRdsMasterSecret, +} from './utils'; + +export const handler = async (event: EventType) => { + const microserviceConfig = getMicroserviceConfig(); + const microserviceName = getMicroserviceName(microserviceConfig, event); + const pgMasterConfig = await getRdsMasterSecret(); + + const client = new Client(pgMasterConfig); + await client.connect(); + console.info('connected to RDS with master credential'); + + // assign db to this role to their own db + console.info('alter database to be owned by this new role'); + const alterDbRoleQuery = `ALTER DATABASE ${microserviceName} OWNER TO ${microserviceName}`; + await executeSqlWithLog(client, alterDbRoleQuery); + + await client.end(); +}; diff --git a/lib/workload/stateless/postgres_manager/function/create-pg-db.ts b/lib/workload/stateless/postgres_manager/function/create-pg-db.ts new file mode 100644 index 000000000..1a51abbf5 --- /dev/null +++ b/lib/workload/stateless/postgres_manager/function/create-pg-db.ts @@ -0,0 +1,30 @@ +import { Client } from 'pg'; +import { + EventType, + getMicroserviceConfig, + getMicroserviceName, + executeSqlWithLog, + getRdsMasterSecret, +} from './utils'; + +export const handler = async (event: EventType) => { + const microserviceConfig = getMicroserviceConfig(); + const microserviceName = getMicroserviceName(microserviceConfig, event); + const pgMasterConfig = await getRdsMasterSecret(); + + const client = new Client(pgMasterConfig); + await client.connect(); + console.info('connected to RDS with master credential'); + + // create microservice db + console.info('create a new database for the given microservice microserviceName'); + const createDbQuery = `CREATE DATABASE ${microserviceName};`; + await executeSqlWithLog(client, createDbQuery); + + // restrict privileged access + console.info('restrict database access from public'); + const restrictPrivilegedQuery = `REVOKE connect ON DATABASE ${microserviceName} FROM PUBLIC;`; + await executeSqlWithLog(client, restrictPrivilegedQuery); + + await client.end(); +}; diff --git a/lib/workload/stateless/postgres_manager/function/create-pg-iam-role.ts b/lib/workload/stateless/postgres_manager/function/create-pg-iam-role.ts new file mode 100644 index 000000000..f9a150ec1 --- /dev/null +++ b/lib/workload/stateless/postgres_manager/function/create-pg-iam-role.ts @@ -0,0 +1,31 @@ +import { Client } from 'pg'; +import { + EventType, + getMicroserviceConfig, + getMicroserviceName, + executeSqlWithLog, + getRdsMasterSecret, + DbAuthType, +} from './utils'; + +export const handler = async (event: EventType) => { + const microserviceConfig = getMicroserviceConfig(); + const microserviceName = getMicroserviceName(microserviceConfig, event); + const pgMasterConfig = await getRdsMasterSecret(); + + const findConfig = microserviceConfig.find((e) => e.name == microserviceName); + if (findConfig?.authType != DbAuthType.RDS_IAM) { + throw new Error('this microservice is not configured for rds_iam'); + } + + const client = new Client(pgMasterConfig); + await client.connect(); + console.info('connected to RDS with master credential'); + + // create a new role + console.info('create new user that has the rds_iam role'); + const assignRdsIamQuery = `CREATE USER ${microserviceName}; GRANT rds_iam TO ${microserviceName};`; + await executeSqlWithLog(client, assignRdsIamQuery); + + await client.end(); +}; diff --git a/lib/workload/stateless/postgres_manager/function/create-pg-login-role.ts b/lib/workload/stateless/postgres_manager/function/create-pg-login-role.ts new file mode 100644 index 000000000..e9510f58b --- /dev/null +++ b/lib/workload/stateless/postgres_manager/function/create-pg-login-role.ts @@ -0,0 +1,89 @@ +import { generate as pass_generator } from 'generate-password'; +import { + executeSqlWithLog, + getMicroserviceName, + getMicroserviceConfig, + getRdsMasterSecret, + DbAuthType, +} from './utils'; +import { + SecretsManagerClient, + CreateSecretCommandInput, + CreateSecretCommand, +} from '@aws-sdk/client-secrets-manager'; +import { Client } from 'pg'; + +type EventType = { + microserviceName: string; +}; + +export const handler = async (event: EventType) => { + const microserviceConfig = getMicroserviceConfig(); + const microserviceName = getMicroserviceName(microserviceConfig, event); + const pgMasterConfig = await getRdsMasterSecret(); + + const findConfig = microserviceConfig.find((e) => e.name == microserviceName); + if (findConfig?.authType != DbAuthType.USERNAME_PASSWORD) { + throw new Error('this microservice is not configured for username-password authentication'); + } + + const client = new Client(pgMasterConfig); + await client.connect(); + console.info('connected to RDS with master credential'); + + // create a new role + console.info('creating a user-password login role'); + const password = pass_generator({ length: 10, numbers: true }); + const createRoleQueryTemplate = `CREATE ROLE ${microserviceName} with LOGIN ENCRYPTED PASSWORD '${password}'`; + await executeSqlWithLog(client, createRoleQueryTemplate); + + await client.end(); + + // store the new db config at secret manager + const secretValue = createSecretValue({ + password: password, + host: pgMasterConfig.host, + port: pgMasterConfig.port, + username: microserviceName, + dbname: microserviceName, + }); + + const smClient = new SecretsManagerClient(); + const smInput: CreateSecretCommandInput = { + Name: `orcabus/microservice/${microserviceName}`, + Description: `orcabus microservice secret for '${microserviceName}'`, + SecretString: JSON.stringify(secretValue), + Tags: [ + { Key: 'stack', Value: 'manual' }, + { Key: 'useCase', Value: 'store credential for the orcabus microservice' }, + { Key: 'creator', Value: 'postgres_microservice at the orcabus stateless stack' }, + ], + }; + const smCommand = new CreateSecretCommand(smInput); + + console.info( + `storing the role credential at the secret manager (orcabus/microservice/${microserviceName})` + ); + const response = await smClient.send(smCommand); + console.info(`ssm-response: ${response}`); +}; + +/** + * create secret manager value for postgres format + */ +const createSecretValue = (props: { + host: string; + username: string; + password: string; + dbname: string; + port: string; +}) => { + return { + engine: 'postgres', + host: props.host, + username: props.username, + password: props.password, + dbname: props.dbname, + port: props.port, + }; +}; diff --git a/lib/workload/stateless/postgres_manager/function/utils.ts b/lib/workload/stateless/postgres_manager/function/utils.ts new file mode 100644 index 000000000..4ee565009 --- /dev/null +++ b/lib/workload/stateless/postgres_manager/function/utils.ts @@ -0,0 +1,100 @@ +import axios from 'axios'; +import { Client } from 'pg'; + +/** + * There are 2 ways of connecting from microservice to db + */ +export enum DbAuthType { + RDS_IAM, + USERNAME_PASSWORD, +} + +export type EventType = { + microserviceName: string; +}; + +export type MicroserviceConfig = { + name: string; + authType: DbAuthType; +}[]; + +/** + * get microservice config from lambda environment + * @returns + */ +export const getMicroserviceConfig = (): MicroserviceConfig => { + if (!process.env.MICROSERVICE_CONFIG) { + throw new Error('no microservice is configured in this lambda'); + } + const microserviceConfig = JSON.parse(process.env.MICROSERVICE_CONFIG) as MicroserviceConfig; + return microserviceConfig; +}; + +/** + * get microservice name from the event payload and validate with the configuration + * @param event + * @returns + */ +export const getMicroserviceName = ( + microserviceConfig: MicroserviceConfig, + event: EventType +): string => { + const eventName = event.microserviceName; + if (!eventName) { + throw new Error('Microservice microserviceName is not defined in the event payload'); + } + + const configNameArray = microserviceConfig.map((v) => v.name); + if (!configNameArray.includes(eventName)) { + throw new Error('invalid event microservice name'); + } + + return eventName; +}; + +/** + * Execute postgres sql statement with some logs + * @param client + * @param sqlStatement + */ +export const executeSqlWithLog = async (client: Client, sqlStatement: string) => { + console.info(`QUERY: ${sqlStatement}`); + console.info(`RESULT: ${JSON.stringify(await client.query(sqlStatement), undefined, 2)}`); +}; + +/** + * get the rds master secret from secret manager and return in pg.Client config format + * @returns + */ +export const getRdsMasterSecret = async () => { + const rdsSecretName = process.env.RDS_SECRET_MANAGER_NAME; + if (!rdsSecretName) throw new Error('No RDS master secret configure in the env variable'); + + const rdsSecret = JSON.parse(await getSecretManagerWithLayerExtension(rdsSecretName)); + + return { + host: rdsSecret.host, + port: rdsSecret.port, + database: rdsSecret.dbname, + user: rdsSecret.username, + password: rdsSecret.password, + }; +}; + +/** + * Wrapper to use the default lambda layer extension to query secret manager + * Ref: https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_lambda.html + * @param secretManagerName The microserviceName of the secret or ARN + * @returns + */ +export const getSecretManagerWithLayerExtension = async (secretManagerName: string) => { + const apiUrl = `http://localhost:2773/secretsmanager/get?secretId=${encodeURIComponent( + secretManagerName + )}`; + const headers = { 'X-Aws-Parameters-Secrets-Token': process.env.AWS_SESSION_TOKEN }; + const res = await axios.get(apiUrl, { + headers: headers, + }); + + return res.data.SecretString; +}; diff --git a/lib/workload/stateless/postgres_manager/package.json b/lib/workload/stateless/postgres_manager/package.json new file mode 100644 index 000000000..c8387c61b --- /dev/null +++ b/lib/workload/stateless/postgres_manager/package.json @@ -0,0 +1,15 @@ +{ + "name": "lambda-with-rds", + "packageManager": "yarn@3.5.1", + "dependencies": { + "@aws-sdk/client-secrets-manager": "^3.515.0", + "axios": "^1.6.7", + "generate-password": "^1.7.1", + "pg": "^8.11.3" + }, + "devDependencies": { + "@types/aws-lambda": "^8.10.134", + "@types/pg": "^8", + "typescript": "^5.3.3" + } +} diff --git a/lib/workload/stateless/postgres_manager/yarn.lock b/lib/workload/stateless/postgres_manager/yarn.lock new file mode 100644 index 000000000..0ef2529e4 --- /dev/null +++ b/lib/workload/stateless/postgres_manager/yarn.lock @@ -0,0 +1,1408 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + cacheKey: 8 + +"@aws-crypto/crc32@npm:3.0.0": + version: 3.0.0 + resolution: "@aws-crypto/crc32@npm:3.0.0" + dependencies: + "@aws-crypto/util": ^3.0.0 + "@aws-sdk/types": ^3.222.0 + tslib: ^1.11.1 + checksum: 9fdb3e837fc54119b017ea34fd0a6d71d2c88075d99e1e818a5158e0ad30ced67ddbcc423a11ceeef6cc465ab5ffd91830acab516470b48237ca7abd51be9642 + languageName: node + linkType: hard + +"@aws-crypto/ie11-detection@npm:^3.0.0": + version: 3.0.0 + resolution: "@aws-crypto/ie11-detection@npm:3.0.0" + dependencies: + tslib: ^1.11.1 + checksum: 299b2ddd46eddac1f2d54d91386ceb37af81aef8a800669281c73d634ed17fd855dcfb8b3157f2879344b93a2666a6d602550eb84b71e4d7868100ad6da8f803 + languageName: node + linkType: hard + +"@aws-crypto/sha256-browser@npm:3.0.0": + version: 3.0.0 + resolution: "@aws-crypto/sha256-browser@npm:3.0.0" + dependencies: + "@aws-crypto/ie11-detection": ^3.0.0 + "@aws-crypto/sha256-js": ^3.0.0 + "@aws-crypto/supports-web-crypto": ^3.0.0 + "@aws-crypto/util": ^3.0.0 + "@aws-sdk/types": ^3.222.0 + "@aws-sdk/util-locate-window": ^3.0.0 + "@aws-sdk/util-utf8-browser": ^3.0.0 + tslib: ^1.11.1 + checksum: ca89456bf508db2e08060a7f656460db97ac9a15b11e39d6fa7665e2b156508a1758695bff8e82d0a00178d6ac5c36f35eb4bcfac2e48621265224ca14a19bd2 + languageName: node + linkType: hard + +"@aws-crypto/sha256-js@npm:3.0.0, @aws-crypto/sha256-js@npm:^3.0.0": + version: 3.0.0 + resolution: "@aws-crypto/sha256-js@npm:3.0.0" + dependencies: + "@aws-crypto/util": ^3.0.0 + "@aws-sdk/types": ^3.222.0 + tslib: ^1.11.1 + checksum: 644ded32ea310237811afae873d3c7320739cb6f6cc39dced9c94801379e68e5ee2cca0c34f0384793fa9e750a7e0a5e2468f95754bd08e6fd72ab833c8fe23c + languageName: node + linkType: hard + +"@aws-crypto/supports-web-crypto@npm:^3.0.0": + version: 3.0.0 + resolution: "@aws-crypto/supports-web-crypto@npm:3.0.0" + dependencies: + tslib: ^1.11.1 + checksum: 35479a1558db9e9a521df6877a99f95670e972c602f2a0349303477e5d638a5baf569fb037c853710e382086e6fd77e8ed58d3fb9b49f6e1186a9d26ce7be006 + languageName: node + linkType: hard + +"@aws-crypto/util@npm:^3.0.0": + version: 3.0.0 + resolution: "@aws-crypto/util@npm:3.0.0" + dependencies: + "@aws-sdk/types": ^3.222.0 + "@aws-sdk/util-utf8-browser": ^3.0.0 + tslib: ^1.11.1 + checksum: d29d5545048721aae3d60b236708535059733019a105f8a64b4e4a8eab7cf8dde1546dc56bff7de20d36140a4d1f0f4693e639c5732a7059273a7b1e56354776 + languageName: node + linkType: hard + +"@aws-sdk/client-secrets-manager@npm:^3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/client-secrets-manager@npm:3.515.0" + dependencies: + "@aws-crypto/sha256-browser": 3.0.0 + "@aws-crypto/sha256-js": 3.0.0 + "@aws-sdk/client-sts": 3.515.0 + "@aws-sdk/core": 3.513.0 + "@aws-sdk/credential-provider-node": 3.515.0 + "@aws-sdk/middleware-host-header": 3.515.0 + "@aws-sdk/middleware-logger": 3.515.0 + "@aws-sdk/middleware-recursion-detection": 3.515.0 + "@aws-sdk/middleware-user-agent": 3.515.0 + "@aws-sdk/region-config-resolver": 3.515.0 + "@aws-sdk/types": 3.515.0 + "@aws-sdk/util-endpoints": 3.515.0 + "@aws-sdk/util-user-agent-browser": 3.515.0 + "@aws-sdk/util-user-agent-node": 3.515.0 + "@smithy/config-resolver": ^2.1.1 + "@smithy/core": ^1.3.2 + "@smithy/fetch-http-handler": ^2.4.1 + "@smithy/hash-node": ^2.1.1 + "@smithy/invalid-dependency": ^2.1.1 + "@smithy/middleware-content-length": ^2.1.1 + "@smithy/middleware-endpoint": ^2.4.1 + "@smithy/middleware-retry": ^2.1.1 + "@smithy/middleware-serde": ^2.1.1 + "@smithy/middleware-stack": ^2.1.1 + "@smithy/node-config-provider": ^2.2.1 + "@smithy/node-http-handler": ^2.3.1 + "@smithy/protocol-http": ^3.1.1 + "@smithy/smithy-client": ^2.3.1 + "@smithy/types": ^2.9.1 + "@smithy/url-parser": ^2.1.1 + "@smithy/util-base64": ^2.1.1 + "@smithy/util-body-length-browser": ^2.1.1 + "@smithy/util-body-length-node": ^2.2.1 + "@smithy/util-defaults-mode-browser": ^2.1.1 + "@smithy/util-defaults-mode-node": ^2.2.0 + "@smithy/util-endpoints": ^1.1.1 + "@smithy/util-middleware": ^2.1.1 + "@smithy/util-retry": ^2.1.1 + "@smithy/util-utf8": ^2.1.1 + tslib: ^2.5.0 + uuid: ^9.0.1 + checksum: a4facfed1da8811fb648732398d7284c07cb21939c03509944cea0bce64b8b9d24a4f554620405cdaaa89c3c4c8473d5dcb89a9efe362a0a314f80c91bf57aeb + languageName: node + linkType: hard + +"@aws-sdk/client-sso-oidc@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/client-sso-oidc@npm:3.515.0" + dependencies: + "@aws-crypto/sha256-browser": 3.0.0 + "@aws-crypto/sha256-js": 3.0.0 + "@aws-sdk/client-sts": 3.515.0 + "@aws-sdk/core": 3.513.0 + "@aws-sdk/middleware-host-header": 3.515.0 + "@aws-sdk/middleware-logger": 3.515.0 + "@aws-sdk/middleware-recursion-detection": 3.515.0 + "@aws-sdk/middleware-user-agent": 3.515.0 + "@aws-sdk/region-config-resolver": 3.515.0 + "@aws-sdk/types": 3.515.0 + "@aws-sdk/util-endpoints": 3.515.0 + "@aws-sdk/util-user-agent-browser": 3.515.0 + "@aws-sdk/util-user-agent-node": 3.515.0 + "@smithy/config-resolver": ^2.1.1 + "@smithy/core": ^1.3.2 + "@smithy/fetch-http-handler": ^2.4.1 + "@smithy/hash-node": ^2.1.1 + "@smithy/invalid-dependency": ^2.1.1 + "@smithy/middleware-content-length": ^2.1.1 + "@smithy/middleware-endpoint": ^2.4.1 + "@smithy/middleware-retry": ^2.1.1 + "@smithy/middleware-serde": ^2.1.1 + "@smithy/middleware-stack": ^2.1.1 + "@smithy/node-config-provider": ^2.2.1 + "@smithy/node-http-handler": ^2.3.1 + "@smithy/protocol-http": ^3.1.1 + "@smithy/smithy-client": ^2.3.1 + "@smithy/types": ^2.9.1 + "@smithy/url-parser": ^2.1.1 + "@smithy/util-base64": ^2.1.1 + "@smithy/util-body-length-browser": ^2.1.1 + "@smithy/util-body-length-node": ^2.2.1 + "@smithy/util-defaults-mode-browser": ^2.1.1 + "@smithy/util-defaults-mode-node": ^2.2.0 + "@smithy/util-endpoints": ^1.1.1 + "@smithy/util-middleware": ^2.1.1 + "@smithy/util-retry": ^2.1.1 + "@smithy/util-utf8": ^2.1.1 + tslib: ^2.5.0 + peerDependencies: + "@aws-sdk/credential-provider-node": ^3.515.0 + checksum: f220a9ba8542460b2aa91ad060302fb9e68bdf096ecca2ec1d6e525f4df1036b330cb85d20bac3e8399276c0d8d8d388b3f2191b58804919c68369afac0be37b + languageName: node + linkType: hard + +"@aws-sdk/client-sso@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/client-sso@npm:3.515.0" + dependencies: + "@aws-crypto/sha256-browser": 3.0.0 + "@aws-crypto/sha256-js": 3.0.0 + "@aws-sdk/core": 3.513.0 + "@aws-sdk/middleware-host-header": 3.515.0 + "@aws-sdk/middleware-logger": 3.515.0 + "@aws-sdk/middleware-recursion-detection": 3.515.0 + "@aws-sdk/middleware-user-agent": 3.515.0 + "@aws-sdk/region-config-resolver": 3.515.0 + "@aws-sdk/types": 3.515.0 + "@aws-sdk/util-endpoints": 3.515.0 + "@aws-sdk/util-user-agent-browser": 3.515.0 + "@aws-sdk/util-user-agent-node": 3.515.0 + "@smithy/config-resolver": ^2.1.1 + "@smithy/core": ^1.3.2 + "@smithy/fetch-http-handler": ^2.4.1 + "@smithy/hash-node": ^2.1.1 + "@smithy/invalid-dependency": ^2.1.1 + "@smithy/middleware-content-length": ^2.1.1 + "@smithy/middleware-endpoint": ^2.4.1 + "@smithy/middleware-retry": ^2.1.1 + "@smithy/middleware-serde": ^2.1.1 + "@smithy/middleware-stack": ^2.1.1 + "@smithy/node-config-provider": ^2.2.1 + "@smithy/node-http-handler": ^2.3.1 + "@smithy/protocol-http": ^3.1.1 + "@smithy/smithy-client": ^2.3.1 + "@smithy/types": ^2.9.1 + "@smithy/url-parser": ^2.1.1 + "@smithy/util-base64": ^2.1.1 + "@smithy/util-body-length-browser": ^2.1.1 + "@smithy/util-body-length-node": ^2.2.1 + "@smithy/util-defaults-mode-browser": ^2.1.1 + "@smithy/util-defaults-mode-node": ^2.2.0 + "@smithy/util-endpoints": ^1.1.1 + "@smithy/util-middleware": ^2.1.1 + "@smithy/util-retry": ^2.1.1 + "@smithy/util-utf8": ^2.1.1 + tslib: ^2.5.0 + checksum: 12287dfa469fb2c6b5bedd3cbd37f7416f8234669b5ed0ff38cb1217d746ba6a5e6ff227b091a7751d1c20489a6b8bd93bcbed8f394cb4c51b6bebb9a9f79108 + languageName: node + linkType: hard + +"@aws-sdk/client-sts@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/client-sts@npm:3.515.0" + dependencies: + "@aws-crypto/sha256-browser": 3.0.0 + "@aws-crypto/sha256-js": 3.0.0 + "@aws-sdk/core": 3.513.0 + "@aws-sdk/middleware-host-header": 3.515.0 + "@aws-sdk/middleware-logger": 3.515.0 + "@aws-sdk/middleware-recursion-detection": 3.515.0 + "@aws-sdk/middleware-user-agent": 3.515.0 + "@aws-sdk/region-config-resolver": 3.515.0 + "@aws-sdk/types": 3.515.0 + "@aws-sdk/util-endpoints": 3.515.0 + "@aws-sdk/util-user-agent-browser": 3.515.0 + "@aws-sdk/util-user-agent-node": 3.515.0 + "@smithy/config-resolver": ^2.1.1 + "@smithy/core": ^1.3.2 + "@smithy/fetch-http-handler": ^2.4.1 + "@smithy/hash-node": ^2.1.1 + "@smithy/invalid-dependency": ^2.1.1 + "@smithy/middleware-content-length": ^2.1.1 + "@smithy/middleware-endpoint": ^2.4.1 + "@smithy/middleware-retry": ^2.1.1 + "@smithy/middleware-serde": ^2.1.1 + "@smithy/middleware-stack": ^2.1.1 + "@smithy/node-config-provider": ^2.2.1 + "@smithy/node-http-handler": ^2.3.1 + "@smithy/protocol-http": ^3.1.1 + "@smithy/smithy-client": ^2.3.1 + "@smithy/types": ^2.9.1 + "@smithy/url-parser": ^2.1.1 + "@smithy/util-base64": ^2.1.1 + "@smithy/util-body-length-browser": ^2.1.1 + "@smithy/util-body-length-node": ^2.2.1 + "@smithy/util-defaults-mode-browser": ^2.1.1 + "@smithy/util-defaults-mode-node": ^2.2.0 + "@smithy/util-endpoints": ^1.1.1 + "@smithy/util-middleware": ^2.1.1 + "@smithy/util-retry": ^2.1.1 + "@smithy/util-utf8": ^2.1.1 + fast-xml-parser: 4.2.5 + tslib: ^2.5.0 + peerDependencies: + "@aws-sdk/credential-provider-node": ^3.515.0 + checksum: 9af6a2484909e88a83c411551d55ad149c80a8f449c2e54c499769535243602a6283cd71f6a0cf975b295a321a74e90eb95f6659bba93bae3d12e2186e7545f4 + languageName: node + linkType: hard + +"@aws-sdk/core@npm:3.513.0": + version: 3.513.0 + resolution: "@aws-sdk/core@npm:3.513.0" + dependencies: + "@smithy/core": ^1.3.2 + "@smithy/protocol-http": ^3.1.1 + "@smithy/signature-v4": ^2.1.1 + "@smithy/smithy-client": ^2.3.1 + "@smithy/types": ^2.9.1 + tslib: ^2.5.0 + checksum: 94a41263e5d0c754f4d6d603572704822b570d5fc5ed450c8eb461b989198b625d2c115a470b087defe2c6c45b9442527062382c9bb1ca32842332317300b2fe + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-env@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/credential-provider-env@npm:3.515.0" + dependencies: + "@aws-sdk/types": 3.515.0 + "@smithy/property-provider": ^2.1.1 + "@smithy/types": ^2.9.1 + tslib: ^2.5.0 + checksum: 3573bc3f1aa89bc8eedb9eb39c8c1d501a68aec5eb059364a1091c8bf10dfda9cfbd78ee49d3ad25ec1012f765a4464363c4cd70997e94005fd21245871a4229 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-http@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/credential-provider-http@npm:3.515.0" + dependencies: + "@aws-sdk/types": 3.515.0 + "@smithy/fetch-http-handler": ^2.4.1 + "@smithy/node-http-handler": ^2.3.1 + "@smithy/property-provider": ^2.1.1 + "@smithy/protocol-http": ^3.1.1 + "@smithy/smithy-client": ^2.3.1 + "@smithy/types": ^2.9.1 + "@smithy/util-stream": ^2.1.1 + tslib: ^2.5.0 + checksum: d13943dc7a83c9c129dd03a8b337b7753c791441d65a894085e00146d807738b420b9127f570a32497665be4f6e1cc8eeefd7cb1b013a04789d874c8b23b829f + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-ini@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/credential-provider-ini@npm:3.515.0" + dependencies: + "@aws-sdk/client-sts": 3.515.0 + "@aws-sdk/credential-provider-env": 3.515.0 + "@aws-sdk/credential-provider-process": 3.515.0 + "@aws-sdk/credential-provider-sso": 3.515.0 + "@aws-sdk/credential-provider-web-identity": 3.515.0 + "@aws-sdk/types": 3.515.0 + "@smithy/credential-provider-imds": ^2.2.1 + "@smithy/property-provider": ^2.1.1 + "@smithy/shared-ini-file-loader": ^2.3.1 + "@smithy/types": ^2.9.1 + tslib: ^2.5.0 + checksum: c136d4257460be8331d7645854b7e3c91205a1eb12efd3dcbcd84501c787085292d1ccc4578c4776308d91748bde5af2c6eaa873b56aed83ab472a878a2d8883 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-node@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/credential-provider-node@npm:3.515.0" + dependencies: + "@aws-sdk/credential-provider-env": 3.515.0 + "@aws-sdk/credential-provider-http": 3.515.0 + "@aws-sdk/credential-provider-ini": 3.515.0 + "@aws-sdk/credential-provider-process": 3.515.0 + "@aws-sdk/credential-provider-sso": 3.515.0 + "@aws-sdk/credential-provider-web-identity": 3.515.0 + "@aws-sdk/types": 3.515.0 + "@smithy/credential-provider-imds": ^2.2.1 + "@smithy/property-provider": ^2.1.1 + "@smithy/shared-ini-file-loader": ^2.3.1 + "@smithy/types": ^2.9.1 + tslib: ^2.5.0 + checksum: c51f267c61de0d82afe47d97cdf58971e3b8eec6e7364fe28d3867addd65e156358b96c39a335fad521e9a6925b349a967ae3eed1aa6e9cb4bcc1f0931e3ed50 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-process@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/credential-provider-process@npm:3.515.0" + dependencies: + "@aws-sdk/types": 3.515.0 + "@smithy/property-provider": ^2.1.1 + "@smithy/shared-ini-file-loader": ^2.3.1 + "@smithy/types": ^2.9.1 + tslib: ^2.5.0 + checksum: 11159b4c9502218ec6cba9a46ddc120e53aec7f04507c14d4e99a186073cfd363af438c623705890a2e8f6cc792475ebd14c82766dad82dabf7f20d9708f7faf + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-sso@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/credential-provider-sso@npm:3.515.0" + dependencies: + "@aws-sdk/client-sso": 3.515.0 + "@aws-sdk/token-providers": 3.515.0 + "@aws-sdk/types": 3.515.0 + "@smithy/property-provider": ^2.1.1 + "@smithy/shared-ini-file-loader": ^2.3.1 + "@smithy/types": ^2.9.1 + tslib: ^2.5.0 + checksum: fbe1eebc50e9bd3715bca4e6ee1a2922cf1ea383953730a6bd3b88b12f23a95cb3ff0edaf8578c81156ef65648eb0295f5c39ed6ae2c8e16b6ce9d4c46f207e9 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-web-identity@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.515.0" + dependencies: + "@aws-sdk/client-sts": 3.515.0 + "@aws-sdk/types": 3.515.0 + "@smithy/property-provider": ^2.1.1 + "@smithy/types": ^2.9.1 + tslib: ^2.5.0 + checksum: f0a7e9855f78849143c3139c613cc1531a88272f3ec039d23ac9366b94495a3e07212ade9541e56c93560ad9f58600376e9de38706a4cd97aaf5f400911361cd + languageName: node + linkType: hard + +"@aws-sdk/middleware-host-header@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/middleware-host-header@npm:3.515.0" + dependencies: + "@aws-sdk/types": 3.515.0 + "@smithy/protocol-http": ^3.1.1 + "@smithy/types": ^2.9.1 + tslib: ^2.5.0 + checksum: ff066cf47b0ba2c64bd70efdec795ac2da8bad7ba8dd44913c98f42b153ca6e753b13b6c1ef7075499590279a5cc49b5a60511dae4512dcdb11a62a0e67fa061 + languageName: node + linkType: hard + +"@aws-sdk/middleware-logger@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/middleware-logger@npm:3.515.0" + dependencies: + "@aws-sdk/types": 3.515.0 + "@smithy/types": ^2.9.1 + tslib: ^2.5.0 + checksum: 32d251e77f43593ffdd192a4d0628f33773e29c14a3001a4c6519553e94958edbd0fb8e6954a65d1180b0caa16cafe9fc9b362d1ab663db1d1eac84b15667645 + languageName: node + linkType: hard + +"@aws-sdk/middleware-recursion-detection@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/middleware-recursion-detection@npm:3.515.0" + dependencies: + "@aws-sdk/types": 3.515.0 + "@smithy/protocol-http": ^3.1.1 + "@smithy/types": ^2.9.1 + tslib: ^2.5.0 + checksum: 23c4a1e4d7de86196acfcfbc84bea84c8c3211c4831fdc7c975a6388022037bd5baa4e5809dca188631f06726b51fcf85af358f9ef526e255dc494b368d0da0c + languageName: node + linkType: hard + +"@aws-sdk/middleware-user-agent@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/middleware-user-agent@npm:3.515.0" + dependencies: + "@aws-sdk/types": 3.515.0 + "@aws-sdk/util-endpoints": 3.515.0 + "@smithy/protocol-http": ^3.1.1 + "@smithy/types": ^2.9.1 + tslib: ^2.5.0 + checksum: fd601cb0367d42e38b71494c773d82bde8970f9aafbdbf18d7cadc25732ecc2b787f0cfef2110755d0cef73d6aa3ce2ca77dc5353854bedaf392a69019b39ac2 + languageName: node + linkType: hard + +"@aws-sdk/region-config-resolver@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/region-config-resolver@npm:3.515.0" + dependencies: + "@aws-sdk/types": 3.515.0 + "@smithy/node-config-provider": ^2.2.1 + "@smithy/types": ^2.9.1 + "@smithy/util-config-provider": ^2.2.1 + "@smithy/util-middleware": ^2.1.1 + tslib: ^2.5.0 + checksum: 0ed7fbd6390baebdf511b30877236fa8be8716e0162e2c9e0138c9b41ebda7d99a6f3d6cf66cb4af24761631c2c29102ecfe7a5f08894e1de3c98ca6a135fa74 + languageName: node + linkType: hard + +"@aws-sdk/token-providers@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/token-providers@npm:3.515.0" + dependencies: + "@aws-sdk/client-sso-oidc": 3.515.0 + "@aws-sdk/types": 3.515.0 + "@smithy/property-provider": ^2.1.1 + "@smithy/shared-ini-file-loader": ^2.3.1 + "@smithy/types": ^2.9.1 + tslib: ^2.5.0 + checksum: ab51c440da9772d0ee58948241b975705c171395cf1bad81a4ffd8f11f34106af54dcba930e360fb19489beedc1106ac14432bd27abdfe4b7536dc2113841027 + languageName: node + linkType: hard + +"@aws-sdk/types@npm:3.515.0, @aws-sdk/types@npm:^3.222.0": + version: 3.515.0 + resolution: "@aws-sdk/types@npm:3.515.0" + dependencies: + "@smithy/types": ^2.9.1 + tslib: ^2.5.0 + checksum: 0874f1814b58eae6e7115c3d08c2bc56e558e73d1ff8c5f833a73b4a0f76a42743c83c36a4b2759177e41b1feff065e85450f7bc235a087b94e67db12f87d298 + languageName: node + linkType: hard + +"@aws-sdk/util-endpoints@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/util-endpoints@npm:3.515.0" + dependencies: + "@aws-sdk/types": 3.515.0 + "@smithy/types": ^2.9.1 + "@smithy/util-endpoints": ^1.1.1 + tslib: ^2.5.0 + checksum: 1ab8fcd3054dc0366f10813a01130d05f4ba33f1488c1a168f44881cb24f3fbc2393111b7b0fd4dc06c852e4c9a5bbe8a82717b72229b0977cdba8a631ddeee1 + languageName: node + linkType: hard + +"@aws-sdk/util-locate-window@npm:^3.0.0": + version: 3.495.0 + resolution: "@aws-sdk/util-locate-window@npm:3.495.0" + dependencies: + tslib: ^2.5.0 + checksum: 73c5fc3199207f8ea19f07668c9412929eca36d91b12ca36212317f78cc0d47afb39a5a5112bee62a0dd62d05c87b22ebb40de23b9dc5efb0d6117d755a00ce0 + languageName: node + linkType: hard + +"@aws-sdk/util-user-agent-browser@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/util-user-agent-browser@npm:3.515.0" + dependencies: + "@aws-sdk/types": 3.515.0 + "@smithy/types": ^2.9.1 + bowser: ^2.11.0 + tslib: ^2.5.0 + checksum: 40f518006cb7e76d06d83dcf05222b0b0ff47c10b63149cd5db2c0c1db79c8eff34bd582e89c748897bc11697b7b357bdca77d569f57ad0b2081c088752d601f + languageName: node + linkType: hard + +"@aws-sdk/util-user-agent-node@npm:3.515.0": + version: 3.515.0 + resolution: "@aws-sdk/util-user-agent-node@npm:3.515.0" + dependencies: + "@aws-sdk/types": 3.515.0 + "@smithy/node-config-provider": ^2.2.1 + "@smithy/types": ^2.9.1 + tslib: ^2.5.0 + peerDependencies: + aws-crt: ">=1.0.0" + peerDependenciesMeta: + aws-crt: + optional: true + checksum: 4e91d9cd5bbe4aa8321417ea1bd9caf3229416ee624b7f67b5206b284a539116692412ca41d60dcb5759b841fed7d9fb570915566d1f7e620578657f89548a23 + languageName: node + linkType: hard + +"@aws-sdk/util-utf8-browser@npm:^3.0.0": + version: 3.259.0 + resolution: "@aws-sdk/util-utf8-browser@npm:3.259.0" + dependencies: + tslib: ^2.3.1 + checksum: b6a1e580da1c9b62c749814182a7649a748ca4253edb4063aa521df97d25b76eae3359eb1680b86f71aac668e05cc05c514379bca39ebf4ba998ae4348412da8 + languageName: node + linkType: hard + +"@smithy/abort-controller@npm:^2.1.2": + version: 2.1.2 + resolution: "@smithy/abort-controller@npm:2.1.2" + dependencies: + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: 0646908e0afce70bc15484bdddff2b862ebdc9566f96e6ff4e0dd7bc430b3c71a503f822786d2fc0f67b109e5c14d58cf29468d43dc2356662f03069c6d41508 + languageName: node + linkType: hard + +"@smithy/config-resolver@npm:^2.1.1, @smithy/config-resolver@npm:^2.1.2": + version: 2.1.2 + resolution: "@smithy/config-resolver@npm:2.1.2" + dependencies: + "@smithy/node-config-provider": ^2.2.2 + "@smithy/types": ^2.10.0 + "@smithy/util-config-provider": ^2.2.1 + "@smithy/util-middleware": ^2.1.2 + tslib: ^2.5.0 + checksum: 20ac9423e416bbb486d1bca247d7a37a2cbffe30c2e292b15c25e411c6cc5af438362ba2aa8e2218e93ef10c7d7fa04873646c4dd82bbcb4df6203efd1f1d3c9 + languageName: node + linkType: hard + +"@smithy/core@npm:^1.3.2": + version: 1.3.3 + resolution: "@smithy/core@npm:1.3.3" + dependencies: + "@smithy/middleware-endpoint": ^2.4.2 + "@smithy/middleware-retry": ^2.1.2 + "@smithy/middleware-serde": ^2.1.2 + "@smithy/protocol-http": ^3.2.0 + "@smithy/smithy-client": ^2.4.0 + "@smithy/types": ^2.10.0 + "@smithy/util-middleware": ^2.1.2 + tslib: ^2.5.0 + checksum: bb8a79f51517049064f1b5fb2233b9410d8c6dfeafa2ecda31b9d5326f05ffa4773c38ec72d90b380366e8a9b4c21b93231aaadce262c0d87989904950358cd3 + languageName: node + linkType: hard + +"@smithy/credential-provider-imds@npm:^2.2.1, @smithy/credential-provider-imds@npm:^2.2.2": + version: 2.2.2 + resolution: "@smithy/credential-provider-imds@npm:2.2.2" + dependencies: + "@smithy/node-config-provider": ^2.2.2 + "@smithy/property-provider": ^2.1.2 + "@smithy/types": ^2.10.0 + "@smithy/url-parser": ^2.1.2 + tslib: ^2.5.0 + checksum: 85cc9a6e2c52a8f47c0db3dd11c31ff550e5ddd0f6d1917169e22fbf242bb5a5c05bc426da5d04bce79cf0633d0c645e83ca02d42e243f3b40c2ace07aeb1b56 + languageName: node + linkType: hard + +"@smithy/eventstream-codec@npm:^2.1.2": + version: 2.1.2 + resolution: "@smithy/eventstream-codec@npm:2.1.2" + dependencies: + "@aws-crypto/crc32": 3.0.0 + "@smithy/types": ^2.10.0 + "@smithy/util-hex-encoding": ^2.1.1 + tslib: ^2.5.0 + checksum: ea455826916906a480c7abc517ceb043b578a95994842f802f71e409c45a5575a4f337c376c0c371055371bebafb81119a044cdb4c7de057304e6b1c0527d1d9 + languageName: node + linkType: hard + +"@smithy/fetch-http-handler@npm:^2.4.1, @smithy/fetch-http-handler@npm:^2.4.2": + version: 2.4.2 + resolution: "@smithy/fetch-http-handler@npm:2.4.2" + dependencies: + "@smithy/protocol-http": ^3.2.0 + "@smithy/querystring-builder": ^2.1.2 + "@smithy/types": ^2.10.0 + "@smithy/util-base64": ^2.1.1 + tslib: ^2.5.0 + checksum: 7d87d5c6674623250972ac673a3317eeaeeba0647d8095c92e63ec9a002e96bb56dd7aa75172e474e226a4971f2abbd2506025cb1bf131d3b45698dbff27220d + languageName: node + linkType: hard + +"@smithy/hash-node@npm:^2.1.1": + version: 2.1.2 + resolution: "@smithy/hash-node@npm:2.1.2" + dependencies: + "@smithy/types": ^2.10.0 + "@smithy/util-buffer-from": ^2.1.1 + "@smithy/util-utf8": ^2.1.1 + tslib: ^2.5.0 + checksum: 2f4fe6120a177afbc540c0ba904a3285a0b81de576a57bb28dbee94186635ab585034e7f48eeff0950d3a6442f5fd932cd66c366199c42f4314a540d02261eba + languageName: node + linkType: hard + +"@smithy/invalid-dependency@npm:^2.1.1": + version: 2.1.2 + resolution: "@smithy/invalid-dependency@npm:2.1.2" + dependencies: + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: 5f5ce3d408c67c3e8b80c7dbc3504662c437ded360c360f072ed731c0bae6b57386ba37dc059883dfe7d23711680a5119a68e05b3ea6ccc7fc124cf0e6f90024 + languageName: node + linkType: hard + +"@smithy/is-array-buffer@npm:^2.1.1": + version: 2.1.1 + resolution: "@smithy/is-array-buffer@npm:2.1.1" + dependencies: + tslib: ^2.5.0 + checksum: 5dbf9ed59715c871321d0624e3842340c1d662d2e8b78313d1658d39eb793b3ac5c379d573eba0a2ca3add9b313848d4d93fd04bb8565c75fbab749928b239a6 + languageName: node + linkType: hard + +"@smithy/middleware-content-length@npm:^2.1.1": + version: 2.1.2 + resolution: "@smithy/middleware-content-length@npm:2.1.2" + dependencies: + "@smithy/protocol-http": ^3.2.0 + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: ddea93b236e5f916da8e1574317967d5aa449e78b0c7153c60c821d117f1648b00effad5301095919de9225810cd8f90f8ee76e7b95c346fc616d8598ad54447 + languageName: node + linkType: hard + +"@smithy/middleware-endpoint@npm:^2.4.1, @smithy/middleware-endpoint@npm:^2.4.2": + version: 2.4.2 + resolution: "@smithy/middleware-endpoint@npm:2.4.2" + dependencies: + "@smithy/middleware-serde": ^2.1.2 + "@smithy/node-config-provider": ^2.2.2 + "@smithy/shared-ini-file-loader": ^2.3.2 + "@smithy/types": ^2.10.0 + "@smithy/url-parser": ^2.1.2 + "@smithy/util-middleware": ^2.1.2 + tslib: ^2.5.0 + checksum: 3e989123fc608c9a32abf30c4033718b3da665a63bd84e8e2869d4aecb0545d461506e75f82b91bbc35a07915ddadf1432643e4c937c11447847a9a45b3de9fa + languageName: node + linkType: hard + +"@smithy/middleware-retry@npm:^2.1.1, @smithy/middleware-retry@npm:^2.1.2": + version: 2.1.2 + resolution: "@smithy/middleware-retry@npm:2.1.2" + dependencies: + "@smithy/node-config-provider": ^2.2.2 + "@smithy/protocol-http": ^3.2.0 + "@smithy/service-error-classification": ^2.1.2 + "@smithy/smithy-client": ^2.4.0 + "@smithy/types": ^2.10.0 + "@smithy/util-middleware": ^2.1.2 + "@smithy/util-retry": ^2.1.2 + tslib: ^2.5.0 + uuid: ^8.3.2 + checksum: ec04fd0c362070529ecd52f2dadd6fd4d638a4e35c62a67309791f1a288550ee8fcd6406ec27fea78d3c89b08a9c6d29ce63bf7cb5a1b0269f86a759b233dac4 + languageName: node + linkType: hard + +"@smithy/middleware-serde@npm:^2.1.1, @smithy/middleware-serde@npm:^2.1.2": + version: 2.1.2 + resolution: "@smithy/middleware-serde@npm:2.1.2" + dependencies: + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: 4f5bd5ee173cf20cd1c12838b0802f96df5a14c3bdab2d50a6009965c128596863c095633958f0c74d82bca3cc9343f8a4c659c033b9a75e614e3a85e34e0665 + languageName: node + linkType: hard + +"@smithy/middleware-stack@npm:^2.1.1, @smithy/middleware-stack@npm:^2.1.2": + version: 2.1.2 + resolution: "@smithy/middleware-stack@npm:2.1.2" + dependencies: + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: f93dda40f08051a6391e213cc3b90f30ebb31399b000bba882cbf37f942786030822e10ccfe576005361ef6341b571460ce791fbdc7dad971c99a33a92e400dd + languageName: node + linkType: hard + +"@smithy/node-config-provider@npm:^2.2.1, @smithy/node-config-provider@npm:^2.2.2": + version: 2.2.2 + resolution: "@smithy/node-config-provider@npm:2.2.2" + dependencies: + "@smithy/property-provider": ^2.1.2 + "@smithy/shared-ini-file-loader": ^2.3.2 + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: 666d80d893985e6af5aa88a2f3ed07bd68c6873805974331fd148ec5a5d331e5116e2dca8656ef57c60e22cec03aee0717061a03ce703a691765bf94d809f2eb + languageName: node + linkType: hard + +"@smithy/node-http-handler@npm:^2.3.1, @smithy/node-http-handler@npm:^2.4.0": + version: 2.4.0 + resolution: "@smithy/node-http-handler@npm:2.4.0" + dependencies: + "@smithy/abort-controller": ^2.1.2 + "@smithy/protocol-http": ^3.2.0 + "@smithy/querystring-builder": ^2.1.2 + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: f4f4171b9b527d142752e2bcf2fc2cc86924bf4f807c435dd8decfdeef88667fd2bba5bb2ed8459f94f0373793aeecfa57960527ffc2ac1a4c71d424f7e7a64b + languageName: node + linkType: hard + +"@smithy/property-provider@npm:^2.1.1, @smithy/property-provider@npm:^2.1.2": + version: 2.1.2 + resolution: "@smithy/property-provider@npm:2.1.2" + dependencies: + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: df2b72912ede1843a75220a458e3ff8ec70e5544c990c0915e615507380cea4c28bb39b425b7ee600f5c3c90d53b5c82ceaf14d641348f465c14640c556ac9bd + languageName: node + linkType: hard + +"@smithy/protocol-http@npm:^3.1.1, @smithy/protocol-http@npm:^3.2.0": + version: 3.2.0 + resolution: "@smithy/protocol-http@npm:3.2.0" + dependencies: + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: a2082228ea933734795db27b3d5c893f31fa6c80154ddb3b8cce46494fb429088f636db202a482fb67686908fd829d20adc04c1786f09e8c67b9835c6eb1a918 + languageName: node + linkType: hard + +"@smithy/querystring-builder@npm:^2.1.2": + version: 2.1.2 + resolution: "@smithy/querystring-builder@npm:2.1.2" + dependencies: + "@smithy/types": ^2.10.0 + "@smithy/util-uri-escape": ^2.1.1 + tslib: ^2.5.0 + checksum: 480bfc64d5beb6da9c0ecdcd6cb0961cf5afa30c5188b32e3a70608e8f5205d6dc56b13e854b5f541dfdb78b9d9e88a349ec6fc5db8f8d00f11ad137e0b81085 + languageName: node + linkType: hard + +"@smithy/querystring-parser@npm:^2.1.2": + version: 2.1.2 + resolution: "@smithy/querystring-parser@npm:2.1.2" + dependencies: + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: 02a1e3a31b37b59adb162d3a2cb084852c2ea01dec948b0669939e77241b05fd7f5b00734418b925248f0b6c164bc483e897438cb2b1a750829f6b4aab0fa8d1 + languageName: node + linkType: hard + +"@smithy/service-error-classification@npm:^2.1.2": + version: 2.1.2 + resolution: "@smithy/service-error-classification@npm:2.1.2" + dependencies: + "@smithy/types": ^2.10.0 + checksum: 8a26f553fd2a823179b701f87c0952e58580c9297166e608a036c7b313d5cc1399bc8b4b056b038003aedc0145643c6c6323e7f683b1a2d1140d9a7982e6bf7c + languageName: node + linkType: hard + +"@smithy/shared-ini-file-loader@npm:^2.3.1, @smithy/shared-ini-file-loader@npm:^2.3.2": + version: 2.3.2 + resolution: "@smithy/shared-ini-file-loader@npm:2.3.2" + dependencies: + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: 6db5ac83a76a15f3bf49496747ef4d20343e87a4de35b87892c5ac5c69a7046ffe7276230a4e9cbc075183d8b0584f1530a878f58324279cf936103c578aa70a + languageName: node + linkType: hard + +"@smithy/signature-v4@npm:^2.1.1": + version: 2.1.2 + resolution: "@smithy/signature-v4@npm:2.1.2" + dependencies: + "@smithy/eventstream-codec": ^2.1.2 + "@smithy/is-array-buffer": ^2.1.1 + "@smithy/types": ^2.10.0 + "@smithy/util-hex-encoding": ^2.1.1 + "@smithy/util-middleware": ^2.1.2 + "@smithy/util-uri-escape": ^2.1.1 + "@smithy/util-utf8": ^2.1.1 + tslib: ^2.5.0 + checksum: c60d6ebd233c88b00de67c9dd0c74356dbab7093b2f8862224fc92d8492379891328e30601cd2068b322da065edcf3da585745fcadae079c7ccb4c05b8b6aed8 + languageName: node + linkType: hard + +"@smithy/smithy-client@npm:^2.3.1, @smithy/smithy-client@npm:^2.4.0": + version: 2.4.0 + resolution: "@smithy/smithy-client@npm:2.4.0" + dependencies: + "@smithy/middleware-endpoint": ^2.4.2 + "@smithy/middleware-stack": ^2.1.2 + "@smithy/protocol-http": ^3.2.0 + "@smithy/types": ^2.10.0 + "@smithy/util-stream": ^2.1.2 + tslib: ^2.5.0 + checksum: af17a6334e0b19323145482d829b664fcc3102cfbea9682753b9bc328840b9bc1968cd3cf64677cdc23c824194061f8fdf3a905d6f71431547a4ec413d557f92 + languageName: node + linkType: hard + +"@smithy/types@npm:^2.10.0, @smithy/types@npm:^2.9.1": + version: 2.10.0 + resolution: "@smithy/types@npm:2.10.0" + dependencies: + tslib: ^2.5.0 + checksum: f353ff0d8f91454288585c7e03485c698af70d46490856abe21d1e2f2154f704a68599328297a1151c1585cd5b0f80a83c759f6d8e02ab168ce616e33cba18c4 + languageName: node + linkType: hard + +"@smithy/url-parser@npm:^2.1.1, @smithy/url-parser@npm:^2.1.2": + version: 2.1.2 + resolution: "@smithy/url-parser@npm:2.1.2" + dependencies: + "@smithy/querystring-parser": ^2.1.2 + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: 83aca5a6474e85d835958caed5b486d5e6438682e6c17a5817ebda48ec9936c68b7b39c35a050f7eb4bd3902e83d8008b26d9fc5df7a168502d6ed93848005c6 + languageName: node + linkType: hard + +"@smithy/util-base64@npm:^2.1.1": + version: 2.1.1 + resolution: "@smithy/util-base64@npm:2.1.1" + dependencies: + "@smithy/util-buffer-from": ^2.1.1 + tslib: ^2.5.0 + checksum: 6dbb93b8745798d56476d37c99dc9f53fe5fc29329b8161fc9e5c55c5a3062916b3e5e4dd596541b248979eefa550d8da7fbb6ab254bf069cb4c920aea6c3590 + languageName: node + linkType: hard + +"@smithy/util-body-length-browser@npm:^2.1.1": + version: 2.1.1 + resolution: "@smithy/util-body-length-browser@npm:2.1.1" + dependencies: + tslib: ^2.5.0 + checksum: 6f7808a41b57a5ab1334f0d036ecec6809a959bcfe6a200f985f35e0c96e72f34fdcb6154873f795835d1d927098055e2dec31ebfb5e5382d1c4c612c80a37c0 + languageName: node + linkType: hard + +"@smithy/util-body-length-node@npm:^2.2.1": + version: 2.2.1 + resolution: "@smithy/util-body-length-node@npm:2.2.1" + dependencies: + tslib: ^2.5.0 + checksum: 6bddc6fac7c9875ae7baaf6088d91192fbe4405bc5c1b69100d52aa1bfebabcc194f5f1b159d8f6f3ade3b54e416f185781970c30a97d4b0a7cec6d02fc490c4 + languageName: node + linkType: hard + +"@smithy/util-buffer-from@npm:^2.1.1": + version: 2.1.1 + resolution: "@smithy/util-buffer-from@npm:2.1.1" + dependencies: + "@smithy/is-array-buffer": ^2.1.1 + tslib: ^2.5.0 + checksum: 8dc7f9afaa356696f14a80cd983a750cbad8eba7c46498ed74fb8ec0cb307f14df64fb10ceb30b2d4792395bb8b216c89155a93dee0f2b3e5cab94fef459a195 + languageName: node + linkType: hard + +"@smithy/util-config-provider@npm:^2.2.1": + version: 2.2.1 + resolution: "@smithy/util-config-provider@npm:2.2.1" + dependencies: + tslib: ^2.5.0 + checksum: f5b34bcf6ef944779f20d7639070e87a521e1a5620e5a91f2d2dbd764824985930a68b71b0b2bde12e1eaac947155789b73a8c09c1aa7ab923f08e42a4173ef4 + languageName: node + linkType: hard + +"@smithy/util-defaults-mode-browser@npm:^2.1.1": + version: 2.1.2 + resolution: "@smithy/util-defaults-mode-browser@npm:2.1.2" + dependencies: + "@smithy/property-provider": ^2.1.2 + "@smithy/smithy-client": ^2.4.0 + "@smithy/types": ^2.10.0 + bowser: ^2.11.0 + tslib: ^2.5.0 + checksum: bc0621f1d5ca46830a4b525def4b829e23336707af28e305758de7f97170024f387a9f3d73e2e29b033ea4963466373df644a304a87291c980159b31cf5ffbcf + languageName: node + linkType: hard + +"@smithy/util-defaults-mode-node@npm:^2.2.0": + version: 2.2.1 + resolution: "@smithy/util-defaults-mode-node@npm:2.2.1" + dependencies: + "@smithy/config-resolver": ^2.1.2 + "@smithy/credential-provider-imds": ^2.2.2 + "@smithy/node-config-provider": ^2.2.2 + "@smithy/property-provider": ^2.1.2 + "@smithy/smithy-client": ^2.4.0 + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: 672c13329e37d61170fb6c997b416fe9216efb6308b2e15560f5a96089bd839018d4705fb541aed0717dd1e1574e3dfc835f37a9608679713ee679d1d7c17642 + languageName: node + linkType: hard + +"@smithy/util-endpoints@npm:^1.1.1": + version: 1.1.2 + resolution: "@smithy/util-endpoints@npm:1.1.2" + dependencies: + "@smithy/node-config-provider": ^2.2.2 + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: 261f383e64116f767cc8e304a647c47261fee8425c43e517511e1bf8ec5e72652b8a12c65259ca4557f3fba477662606253d4cf89ccab5af184a05c59d2d735f + languageName: node + linkType: hard + +"@smithy/util-hex-encoding@npm:^2.1.1": + version: 2.1.1 + resolution: "@smithy/util-hex-encoding@npm:2.1.1" + dependencies: + tslib: ^2.5.0 + checksum: eae5c94fd4d57dccbae5ad4d7684787b1e9b1df944cf9fcb497cbefaed6aec49c0a777cc1ea4d10fa7002b82f0b73b8830ae2efe98ed35a62dcf3c4f7d08a4cd + languageName: node + linkType: hard + +"@smithy/util-middleware@npm:^2.1.1, @smithy/util-middleware@npm:^2.1.2": + version: 2.1.2 + resolution: "@smithy/util-middleware@npm:2.1.2" + dependencies: + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: 8a05c05ba1358515aa6881189cd4a6a57701e8cd9e036c8d7219662fd12bce1695a4970c247314e0020f13bb506a558545ab5c519373647be89d79af08d5bcdf + languageName: node + linkType: hard + +"@smithy/util-retry@npm:^2.1.1, @smithy/util-retry@npm:^2.1.2": + version: 2.1.2 + resolution: "@smithy/util-retry@npm:2.1.2" + dependencies: + "@smithy/service-error-classification": ^2.1.2 + "@smithy/types": ^2.10.0 + tslib: ^2.5.0 + checksum: 3be4b984b0f1daa54948fe158568a41003f725464ef32f0ccf32e02b566545364bd6f89a380a218397742edd1ad1d214906fab27debce531c137bfceca0c9c6d + languageName: node + linkType: hard + +"@smithy/util-stream@npm:^2.1.1, @smithy/util-stream@npm:^2.1.2": + version: 2.1.2 + resolution: "@smithy/util-stream@npm:2.1.2" + dependencies: + "@smithy/fetch-http-handler": ^2.4.2 + "@smithy/node-http-handler": ^2.4.0 + "@smithy/types": ^2.10.0 + "@smithy/util-base64": ^2.1.1 + "@smithy/util-buffer-from": ^2.1.1 + "@smithy/util-hex-encoding": ^2.1.1 + "@smithy/util-utf8": ^2.1.1 + tslib: ^2.5.0 + checksum: 8b95535323fcf3ce86cbb070791405afd4de1513a38fef209bfc4d5b2ed91ae16ae40393dd8a5f8b127194ec023cc264808f87beef0678219f56b2bcb580eb65 + languageName: node + linkType: hard + +"@smithy/util-uri-escape@npm:^2.1.1": + version: 2.1.1 + resolution: "@smithy/util-uri-escape@npm:2.1.1" + dependencies: + tslib: ^2.5.0 + checksum: 822ed7390e28d5c7b8dab5e5c5a8de998e0778220137962a71b47b2d8900289d48a3a2c9945e68e1cac921d43f61660045e7fdffe8df9e63004575fcf2aa99b2 + languageName: node + linkType: hard + +"@smithy/util-utf8@npm:^2.1.1": + version: 2.1.1 + resolution: "@smithy/util-utf8@npm:2.1.1" + dependencies: + "@smithy/util-buffer-from": ^2.1.1 + tslib: ^2.5.0 + checksum: 286ce5cba3f45a8abd3d6c28e40b3204dd64300340818d77e42c1cbb0c2f6ad0c42f0e47ffaf38d74d0895b0dfd1750c5b55222ab4d205a3b39da4325971d303 + languageName: node + linkType: hard + +"@types/aws-lambda@npm:^8.10.134": + version: 8.10.134 + resolution: "@types/aws-lambda@npm:8.10.134" + checksum: ae485b21c3cbd22d93c17a0723bbc8b533644d708e5338551b86d1f58b501b175769d52ed6549917df7673832ce6d47de25e689b380cd7b72d4931425626e45b + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 20.11.19 + resolution: "@types/node@npm:20.11.19" + dependencies: + undici-types: ~5.26.4 + checksum: 259d16643ba611ade617a8212e594a3ac014727457507389bbf7213971346ab052d870f1e6e2df0afd0876ecd7874f578bccb130be01e069263cfc7136ddc0c1 + languageName: node + linkType: hard + +"@types/pg@npm:^8": + version: 8.11.0 + resolution: "@types/pg@npm:8.11.0" + dependencies: + "@types/node": "*" + pg-protocol: "*" + pg-types: ^4.0.1 + checksum: 8ae18abce86a012afdd68b2fb85a9fd0e9529f2dae8ca64311a4804fc8423441d605df51f547170efa4584c6ee9f919b4f5f731d5a6221386c5d04560de4334c + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 7b78c451df768adba04e2d02e63e2d0bf3b07adcd6e42b4cf665cb7ce899bedd344c69a1dcbce355b5f972d597b25aaa1c1742b52cffd9caccb22f348114f6be + languageName: node + linkType: hard + +"axios@npm:^1.6.7": + version: 1.6.7 + resolution: "axios@npm:1.6.7" + dependencies: + follow-redirects: ^1.15.4 + form-data: ^4.0.0 + proxy-from-env: ^1.1.0 + checksum: 87d4d429927d09942771f3b3a6c13580c183e31d7be0ee12f09be6d5655304996bb033d85e54be81606f4e89684df43be7bf52d14becb73a12727bf33298a082 + languageName: node + linkType: hard + +"bowser@npm:^2.11.0": + version: 2.11.0 + resolution: "bowser@npm:2.11.0" + checksum: 29c3f01f22e703fa6644fc3b684307442df4240b6e10f6cfe1b61c6ca5721073189ca97cdeedb376081148c8518e33b1d818a57f781d70b0b70e1f31fb48814f + languageName: node + linkType: hard + +"buffer-writer@npm:2.0.0": + version: 2.0.0 + resolution: "buffer-writer@npm:2.0.0" + checksum: 11736b48bb75106c52ca8ec9f025e7c1b3b25ce31875f469d7210eabd5c576c329e34f6b805d4a8d605ff3f0db1e16342328802c4c963e9c826b0e43a4e631c2 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: ~1.0.0 + checksum: 49fa4aeb4916567e33ea81d088f6584749fc90c7abec76fd516bf1c5aa5c79f3584b5ba3de6b86d26ddd64bae5329c4c7479343250cfe71c75bb366eae53bb7c + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 46fe6e83e2cb1d85ba50bd52803c68be9bd953282fa7096f51fc29edd5d67ff84ff753c51966061e5ba7cb5e47ef6d36a91924eddb7f3f3483b1c560f77a0020 + languageName: node + linkType: hard + +"fast-xml-parser@npm:4.2.5": + version: 4.2.5 + resolution: "fast-xml-parser@npm:4.2.5" + dependencies: + strnum: ^1.0.5 + bin: + fxparser: src/cli/cli.js + checksum: d32b22005504eeb207249bf40dc82d0994b5bb9ca9dcc731d335a1f425e47fe085b3cace3cf9d32172dd1a5544193c49e8615ca95b4bf95a4a4920a226b06d80 + languageName: node + linkType: hard + +"follow-redirects@npm:^1.15.4": + version: 1.15.5 + resolution: "follow-redirects@npm:1.15.5" + peerDependenciesMeta: + debug: + optional: true + checksum: 5ca49b5ce6f44338cbfc3546823357e7a70813cecc9b7b768158a1d32c1e62e7407c944402a918ea8c38ae2e78266312d617dc68783fac502cbb55e1047b34ec + languageName: node + linkType: hard + +"form-data@npm:^4.0.0": + version: 4.0.0 + resolution: "form-data@npm:4.0.0" + dependencies: + asynckit: ^0.4.0 + combined-stream: ^1.0.8 + mime-types: ^2.1.12 + checksum: 01135bf8675f9d5c61ff18e2e2932f719ca4de964e3be90ef4c36aacfc7b9cb2fceb5eca0b7e0190e3383fe51c5b37f4cb80b62ca06a99aaabfcfd6ac7c9328c + languageName: node + linkType: hard + +"generate-password@npm:^1.7.1": + version: 1.7.1 + resolution: "generate-password@npm:1.7.1" + checksum: 76ab72dcf13ae174e4e069b32b94d7c2613c6e83db1c00ffad17a480a80875b28c4ed8f0767b143f7103b0742afb45a10549e6ef2f72c0a994979e77b8e95b86 + languageName: node + linkType: hard + +"lambda-with-rds@workspace:.": + version: 0.0.0-use.local + resolution: "lambda-with-rds@workspace:." + dependencies: + "@aws-sdk/client-secrets-manager": ^3.515.0 + "@types/aws-lambda": ^8.10.134 + "@types/pg": ^8 + axios: ^1.6.7 + generate-password: ^1.7.1 + pg: ^8.11.3 + typescript: ^5.3.3 + languageName: unknown + linkType: soft + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 0d99a03585f8b39d68182803b12ac601d9c01abfa28ec56204fa330bc9f3d1c5e14beb049bafadb3dbdf646dfb94b87e24d4ec7b31b7279ef906a8ea9b6a513f + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: 1.52.0 + checksum: 89a5b7f1def9f3af5dad6496c5ed50191ae4331cc5389d7c521c8ad28d5fdad2d06fd81baf38fed813dc4e46bb55c8145bb0ff406330818c9cf712fb2e9b3836 + languageName: node + linkType: hard + +"obuf@npm:~1.1.2": + version: 1.1.2 + resolution: "obuf@npm:1.1.2" + checksum: 41a2ba310e7b6f6c3b905af82c275bf8854896e2e4c5752966d64cbcd2f599cfffd5932006bcf3b8b419dfdacebb3a3912d5d94e10f1d0acab59876c8757f27f + languageName: node + linkType: hard + +"packet-reader@npm:1.0.0": + version: 1.0.0 + resolution: "packet-reader@npm:1.0.0" + checksum: 0b7516f0cbf3e322aad591bed29ba544220088c53943145c0d9121a6f59182ad811f7fd6785a8979a34356aca69d97653689029964c5998dc02645633d88ffd7 + languageName: node + linkType: hard + +"pg-cloudflare@npm:^1.1.1": + version: 1.1.1 + resolution: "pg-cloudflare@npm:1.1.1" + checksum: 32aac06b5dc4588bbf78801b6267781bc7e13be672009df949d08e9627ba9fdc26924916665d4de99d47f9b0495301930547488dad889d826856976c7b3f3731 + languageName: node + linkType: hard + +"pg-connection-string@npm:^2.6.2": + version: 2.6.2 + resolution: "pg-connection-string@npm:2.6.2" + checksum: 22265882c3b6f2320785378d0760b051294a684989163d5a1cde4009e64e84448d7bf67d9a7b9e7f69440c3ee9e2212f9aa10dd17ad6773f6143c6020cebbcb5 + languageName: node + linkType: hard + +"pg-int8@npm:1.0.1": + version: 1.0.1 + resolution: "pg-int8@npm:1.0.1" + checksum: a1e3a05a69005ddb73e5f324b6b4e689868a447c5fa280b44cd4d04e6916a344ac289e0b8d2695d66e8e89a7fba023affb9e0e94778770ada5df43f003d664c9 + languageName: node + linkType: hard + +"pg-numeric@npm:1.0.2": + version: 1.0.2 + resolution: "pg-numeric@npm:1.0.2" + checksum: 8899f8200caa1744439a8778a9eb3ceefb599d893e40a09eef84ee0d4c151319fd416634a6c0fc7b7db4ac268710042da5be700b80ef0de716fe089b8652c84f + languageName: node + linkType: hard + +"pg-pool@npm:^3.6.1": + version: 3.6.1 + resolution: "pg-pool@npm:3.6.1" + peerDependencies: + pg: ">=8.0" + checksum: 8a6513e6f74a794708c9dd16d2ccda0debadc56435ec2582de2b2e35b01315550c5dab8a0a9a2a16f4adce45523228f5739940fb7687ec7e9c300f284eb08fd1 + languageName: node + linkType: hard + +"pg-protocol@npm:*, pg-protocol@npm:^1.6.0": + version: 1.6.0 + resolution: "pg-protocol@npm:1.6.0" + checksum: e12662d2de2011e0c3a03f6a09f435beb1025acdc860f181f18a600a5495dc38a69d753bbde1ace279c8c442536af9c1a7c11e1d0fe3fad3aa1348b28d9d2683 + languageName: node + linkType: hard + +"pg-types@npm:^2.1.0": + version: 2.2.0 + resolution: "pg-types@npm:2.2.0" + dependencies: + pg-int8: 1.0.1 + postgres-array: ~2.0.0 + postgres-bytea: ~1.0.0 + postgres-date: ~1.0.4 + postgres-interval: ^1.1.0 + checksum: bf4ec3f594743442857fb3a8dfe5d2478a04c98f96a0a47365014557cbc0b4b0cee01462c79adca863b93befbf88f876299b75b72c665b5fb84a2c94fbd10316 + languageName: node + linkType: hard + +"pg-types@npm:^4.0.1": + version: 4.0.2 + resolution: "pg-types@npm:4.0.2" + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: ~3.0.1 + postgres-bytea: ~3.0.0 + postgres-date: ~2.1.0 + postgres-interval: ^3.0.0 + postgres-range: ^1.1.1 + checksum: c4b813382d4a75f87462fab3245d5422b86ba1a54a1b330e6b43a459c127b4d02553dc7e5b4ae4fa0f5f17971d416eb393810f69ff6d30d986e45c2f20778c55 + languageName: node + linkType: hard + +"pg@npm:^8.11.3": + version: 8.11.3 + resolution: "pg@npm:8.11.3" + dependencies: + buffer-writer: 2.0.0 + packet-reader: 1.0.0 + pg-cloudflare: ^1.1.1 + pg-connection-string: ^2.6.2 + pg-pool: ^3.6.1 + pg-protocol: ^1.6.0 + pg-types: ^2.1.0 + pgpass: 1.x + peerDependencies: + pg-native: ">=3.0.1" + dependenciesMeta: + pg-cloudflare: + optional: true + peerDependenciesMeta: + pg-native: + optional: true + checksum: 8af9468b8969fa0d73a6b349216c8cbc953d938fcae5594f2d24043060e9226a072c8085fc4230172b5576fcab4c39c8563c655f271dc2a9209b6ad5370cafe5 + languageName: node + linkType: hard + +"pgpass@npm:1.x": + version: 1.0.5 + resolution: "pgpass@npm:1.0.5" + dependencies: + split2: ^4.1.0 + checksum: 947ac096c031eebdf08d989de2e9f6f156b8133d6858c7c2c06c041e1e71dda6f5f3bad3c0ec1e96a09497bbc6ef89e762eefe703b5ef9cb2804392ec52ec400 + languageName: node + linkType: hard + +"postgres-array@npm:~2.0.0": + version: 2.0.0 + resolution: "postgres-array@npm:2.0.0" + checksum: 0e1e659888147c5de579d229a2d95c0d83ebdbffc2b9396d890a123557708c3b758a0a97ed305ce7f58edfa961fa9f0bbcd1ea9f08b6e5df73322e683883c464 + languageName: node + linkType: hard + +"postgres-array@npm:~3.0.1": + version: 3.0.2 + resolution: "postgres-array@npm:3.0.2" + checksum: 5955f9dffeb6fa960c1a0b04fd4b2ba16813ddb636934ad26f902e4d76a91c0b743dcc6edc4cffc52deba7d547505e0020adea027c1d50a774f989cf955420d1 + languageName: node + linkType: hard + +"postgres-bytea@npm:~1.0.0": + version: 1.0.0 + resolution: "postgres-bytea@npm:1.0.0" + checksum: d844ae4ca7a941b70e45cac1261a73ee8ed39d72d3d74ab1d645248185a1b7f0ac91a3c63d6159441020f4e1f7fe64689ac56536a307b31cef361e5187335090 + languageName: node + linkType: hard + +"postgres-bytea@npm:~3.0.0": + version: 3.0.0 + resolution: "postgres-bytea@npm:3.0.0" + dependencies: + obuf: ~1.1.2 + checksum: 5f917a003fcaa0df7f285e1c37108ad474ce91193466b9bd4bcaecef2cdea98ca069c00aa6a8dbe6d2e7192336cadc3c9b36ae48d1555a299521918e00e2936b + languageName: node + linkType: hard + +"postgres-date@npm:~1.0.4": + version: 1.0.7 + resolution: "postgres-date@npm:1.0.7" + checksum: 5745001d47e51cd767e46bcb1710649cd705d91a24d42fa661c454b6dcbb7353c066a5047983c90a626cd3bbfea9e626cc6fa84a35ec57e5bbb28b49f78e13ed + languageName: node + linkType: hard + +"postgres-date@npm:~2.1.0": + version: 2.1.0 + resolution: "postgres-date@npm:2.1.0" + checksum: 5c573b0602e17c6134fd8bc8ac7689ac0302e1b199f15dd3578fc45186f206dbd0609f97bf0e4bd1db62234d7a37f29c04f4df525f7efebb9304363b2efca272 + languageName: node + linkType: hard + +"postgres-interval@npm:^1.1.0": + version: 1.2.0 + resolution: "postgres-interval@npm:1.2.0" + dependencies: + xtend: ^4.0.0 + checksum: 746b71f93805ae33b03528e429dc624706d1f9b20ee81bf743263efb6a0cd79ae02a642a8a480dbc0f09547b4315ab7df6ce5ec0be77ed700bac42730f5c76b2 + languageName: node + linkType: hard + +"postgres-interval@npm:^3.0.0": + version: 3.0.0 + resolution: "postgres-interval@npm:3.0.0" + checksum: c7a1cf006de97de663b6b8c4d2b167aa9909a238c4866a94b15d303762f5ac884ff4796cd6e2111b7f0a91302b83c570453aa8506fd005b5a5d5dfa87441bebc + languageName: node + linkType: hard + +"postgres-range@npm:^1.1.1": + version: 1.1.4 + resolution: "postgres-range@npm:1.1.4" + checksum: 460af8c882a50e2c3d08ede5d5ee9e5e5a99dcf471e3ed55b4c17cad62dc85177b51bb8105b626a9c73de9edcba934e86665923b0d86e1c8e1f55d3e0f3530c6 + languageName: node + linkType: hard + +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4 + languageName: node + linkType: hard + +"split2@npm:^4.1.0": + version: 4.2.0 + resolution: "split2@npm:4.2.0" + checksum: 05d54102546549fe4d2455900699056580cca006c0275c334611420f854da30ac999230857a85fdd9914dc2109ae50f80fda43d2a445f2aa86eccdc1dfce779d + languageName: node + linkType: hard + +"strnum@npm:^1.0.5": + version: 1.0.5 + resolution: "strnum@npm:1.0.5" + checksum: 651b2031db5da1bf4a77fdd2f116a8ac8055157c5420f5569f64879133825915ad461513e7202a16d7fec63c54fd822410d0962f8ca12385c4334891b9ae6dd2 + languageName: node + linkType: hard + +"tslib@npm:^1.11.1": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd + languageName: node + linkType: hard + +"tslib@npm:^2.3.1, tslib@npm:^2.5.0": + version: 2.6.2 + resolution: "tslib@npm:2.6.2" + checksum: 329ea56123005922f39642318e3d1f0f8265d1e7fcb92c633e0809521da75eeaca28d2cf96d7248229deb40e5c19adf408259f4b9640afd20d13aecc1430f3ad + languageName: node + linkType: hard + +"typescript@npm:^5.3.3": + version: 5.3.3 + resolution: "typescript@npm:5.3.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 2007ccb6e51bbbf6fde0a78099efe04dc1c3dfbdff04ca3b6a8bc717991862b39fd6126c0c3ebf2d2d98ac5e960bcaa873826bb2bb241f14277034148f41f6a2 + languageName: node + linkType: hard + +"typescript@patch:typescript@^5.3.3#~builtin": + version: 5.3.3 + resolution: "typescript@patch:typescript@npm%3A5.3.3#~builtin::version=5.3.3&hash=77c9e2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: f61375590b3162599f0f0d5b8737877ac0a7bc52761dbb585d67e7b8753a3a4c42d9a554c4cc929f591ffcf3a2b0602f65ae3ce74714fd5652623a816862b610 + languageName: node + linkType: hard + +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: 3192ef6f3fd5df652f2dc1cd782b49d6ff14dc98e5dced492aa8a8c65425227da5da6aafe22523c67f035a272c599bb89cfe803c1db6311e44bed3042fc25487 + languageName: node + linkType: hard + +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 5575a8a75c13120e2f10e6ddc801b2c7ed7d8f3c8ac22c7ed0c7b2ba6383ec0abda88c905085d630e251719e0777045ae3236f04c812184b7c765f63a70e58df + languageName: node + linkType: hard + +"uuid@npm:^9.0.1": + version: 9.0.1 + resolution: "uuid@npm:9.0.1" + bin: + uuid: dist/bin/uuid + checksum: 39931f6da74e307f51c0fb463dc2462807531dc80760a9bff1e35af4316131b4fc3203d16da60ae33f07fdca5b56f3f1dd662da0c99fea9aaeab2004780cc5f4 + languageName: node + linkType: hard + +"xtend@npm:^4.0.0": + version: 4.0.2 + resolution: "xtend@npm:4.0.2" + checksum: ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a + languageName: node + linkType: hard From 0031c983890a0726719e11aeee85270e7e09f821 Mon Sep 17 00:00:00 2001 From: William Putra Intan <61998484+williamputraintan@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:53:25 +1100 Subject: [PATCH 02/10] Update README.md --- lib/workload/stateless/postgres_manager/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/workload/stateless/postgres_manager/README.md b/lib/workload/stateless/postgres_manager/README.md index 25d19382b..7e9382935 100644 --- a/lib/workload/stateless/postgres_manager/README.md +++ b/lib/workload/stateless/postgres_manager/README.md @@ -11,13 +11,16 @@ make use of `rds_iam` or the conventional `user-password` connection string. The microservice config should look as follows: -```json +```ts microserviceDbConfig: [ { name: 'metadata_manager', authType: DbAuthType.USERNAME_PASSWORD, }, - { name: 'filemanager', authType: DbAuthType.RDS_IAM }, + { + name: 'filemanager', + authType: DbAuthType.RDS_IAM + }, ] ``` From 772935b5f4ae27407c115a9fe81bddb44c2e6e1d Mon Sep 17 00:00:00 2001 From: William Putra Intan <61998484+williamputraintan@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:57:23 +1100 Subject: [PATCH 03/10] Update orcabus-stateless-stack.ts --- lib/workload/orcabus-stateless-stack.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/workload/orcabus-stateless-stack.ts b/lib/workload/orcabus-stateless-stack.ts index f13b59c8a..94bcbc9f5 100644 --- a/lib/workload/orcabus-stateless-stack.ts +++ b/lib/workload/orcabus-stateless-stack.ts @@ -66,12 +66,12 @@ export class OrcaBusStatelessStack extends cdk.Stack { this.createPostgresManager(props.postgresManagerConfig); - // if (props.filemanagerDependencies) { - // this.createFilemanager({ - // ...props.filemanagerDependencies, - // lambdaSecurityGroupName: props.lambdaSecurityGroupName, - // }); - // } + if (props.filemanagerDependencies) { + this.createFilemanager({ + ...props.filemanagerDependencies, + lambdaSecurityGroupName: props.lambdaSecurityGroupName, + }); + } } private createSequenceRunManager() { From 442e79bed2fd02e10a0fac9955df45b04caf30fb Mon Sep 17 00:00:00 2001 From: William Putra Intan <61998484+williamputraintan@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:01:48 +1100 Subject: [PATCH 04/10] refine --- .../construct/postgresManager.ts | 8 +- .../postgres_manager/deploy/stack.ts | 127 ------------------ 2 files changed, 4 insertions(+), 131 deletions(-) delete mode 100644 lib/workload/stateless/postgres_manager/deploy/stack.ts diff --git a/lib/workload/stateless/postgres_manager/construct/postgresManager.ts b/lib/workload/stateless/postgres_manager/construct/postgresManager.ts index 09eeb1030..924da4863 100644 --- a/lib/workload/stateless/postgres_manager/construct/postgresManager.ts +++ b/lib/workload/stateless/postgres_manager/construct/postgresManager.ts @@ -65,7 +65,7 @@ export class PostgresManager extends Construct { securityGroups: [props.lambdaSecurityGroup], }; - // create new database lambda + // 1. lambda responsible on db creation const createPgDb = new nodejs.NodejsFunction(this, 'CreateDbPostgresLambda', { ...rdsLambdaProps, entry: __dirname + '/../function/create-pg-db.ts', @@ -73,7 +73,7 @@ export class PostgresManager extends Construct { }); masterSecret.grantRead(createPgDb); - // create role which has the rds-iam + // 2. lambda responsible on role creation with rds_iam const initiatePgRdsIam = new nodejs.NodejsFunction(this, 'CreateIamUserPostgresLambda', { ...rdsLambdaProps, entry: __dirname + '/../function/create-pg-iam-role.ts', @@ -101,7 +101,7 @@ export class PostgresManager extends Construct { } } - // create role with username-password login + // 3. lambda responsible on role creation with username-password auth const createRolePgLambda = new nodejs.NodejsFunction(this, 'CreateUserPassPostgresLambda', { ...rdsLambdaProps, initialPolicy: [ @@ -116,7 +116,7 @@ export class PostgresManager extends Construct { }); masterSecret.grantRead(createRolePgLambda); - // alter db owner to its respective role + // 4. lambda responsible on alter db owner const alterDbPgOwnerLambda = new nodejs.NodejsFunction(this, 'AlterDbOwnerPostgresLambda', { ...rdsLambdaProps, entry: __dirname + '/../function/alter-pg-db-owner.ts', diff --git a/lib/workload/stateless/postgres_manager/deploy/stack.ts b/lib/workload/stateless/postgres_manager/deploy/stack.ts deleted file mode 100644 index aebd4364a..000000000 --- a/lib/workload/stateless/postgres_manager/deploy/stack.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { Duration } from 'aws-cdk-lib'; -import { Construct } from 'constructs'; -import * as lambda from 'aws-cdk-lib/aws-lambda'; -import * as nodejs from 'aws-cdk-lib/aws-lambda-nodejs'; -import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; -import * as ec2 from 'aws-cdk-lib/aws-ec2'; -import * as iam from 'aws-cdk-lib/aws-iam'; -import * as rds from 'aws-cdk-lib/aws-rds'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; -import { DbAuthType, MicroserviceConfig } from '../function/utils'; - -export type PostgresManagerStackConfig = { - masterSecretName: string; - dbClusterIdentifier: string; - microserviceDbConfig: MicroserviceConfig; - clusterResourceIdParameterName: string; -}; - -export type PostgresManagerStackProps = PostgresManagerStackConfig & { - vpc: ec2.IVpc; - lambdaSecurityGroup: ec2.ISecurityGroup; -}; - -export class PostgresManagerStack extends Construct { - constructor(scope: Construct, id: string, props: PostgresManagerStackProps) { - super(scope, id); - - const { dbClusterIdentifier, microserviceDbConfig } = props; - - const masterSecret = secretsmanager.Secret.fromSecretNameV2( - this, - 'RdsMasterSecret', - props.masterSecretName - ); - - const dbClusterResourceId = ssm.StringParameter.valueFromLookup( - this, - '/orcabus/db-cluster-resource-id' - ); - - // Let lambda access secret manager via aws managed lambda extension - // Ref: - // https://aws.amazon.com/blogs/compute/using-the-aws-parameter-and-secrets-lambda-extension-to-cache-parameters-and-secrets/ - const lambdaLayerGetSecretExtension = lambda.LayerVersion.fromLayerVersionArn( - this, - 'GetSecretExtensionLayer', - 'arn:aws:lambda:ap-southeast-2:665172237481:layer:AWS-Parameters-and-Secrets-Lambda-Extension-Arm64:11' - ); - - const rdsLambdaProps = { - timeout: Duration.minutes(5), - depsLockFilePath: __dirname + '/../yarn.lock', - handler: 'handler', - layers: [lambdaLayerGetSecretExtension], - runtime: lambda.Runtime.NODEJS_20_X, - architecture: lambda.Architecture.ARM_64, - environment: { - RDS_SECRET_MANAGER_NAME: masterSecret.secretName, - MICROSERVICE_CONFIG: JSON.stringify(props.microserviceDbConfig), - }, - vpc: props.vpc, - vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, - }, - securityGroups: [props.lambdaSecurityGroup], - }; - - // create new database lambda - const createPgDb = new nodejs.NodejsFunction(this, 'CreateDbPostgresLambda', { - ...rdsLambdaProps, - entry: __dirname + '/../function/create-pg-db.ts', - functionName: 'orcabus-create-pg-db', - }); - masterSecret.grantRead(createPgDb); - - // create role which has the rds-iam - const initiatePgRdsIam = new nodejs.NodejsFunction(this, 'CreateIamUserPostgresLambda', { - ...rdsLambdaProps, - entry: __dirname + '/../function/create-pg-iam-role.ts', - functionName: 'orcabus-create-pg-iam-role', - }); - masterSecret.grantRead(initiatePgRdsIam); - - // create iam-policy that could be assumed when using the rds-iam - for (const microservice of microserviceDbConfig) { - if (microservice.authType == DbAuthType.RDS_IAM) { - const iamPolicy = new iam.ManagedPolicy(this, `${microservice.name}RdsIamPolicy`, { - managedPolicyName: `orcabus-rds-connect-${microservice.name}`, - }); - - const dbCluster = rds.DatabaseCluster.fromDatabaseClusterAttributes( - this, - 'OrcabusDbCluster', - { - clusterIdentifier: dbClusterIdentifier, - clusterResourceIdentifier: dbClusterResourceId, - } - ); - - dbCluster.grantConnect(iamPolicy, microservice.name); - } - } - - // create role with username-password login - const createRolePgLambda = new nodejs.NodejsFunction(this, 'CreateUserPassPostgresLambda', { - ...rdsLambdaProps, - initialPolicy: [ - new iam.PolicyStatement({ - actions: ['secretsmanager:CreateSecret', 'secretsmanager:TagResource'], - effect: iam.Effect.ALLOW, - resources: ['arn:aws:secretsmanager:ap-southeast-2:*:secret:*'], - }), - ], - entry: __dirname + '/../function/create-pg-login-role.ts', - functionName: 'orcabus-create-pg-login-role', - }); - masterSecret.grantRead(createRolePgLambda); - - // alter db owner to its respective role - const alterDbPgOwnerLambda = new nodejs.NodejsFunction(this, 'AlterDbOwnerPassPostgresLambda', { - ...rdsLambdaProps, - entry: __dirname + '/../function/alter-pg-db-owner.ts', - functionName: 'orcabus-alter-pg-db-owner', - }); - masterSecret.grantRead(alterDbPgOwnerLambda); - } -} From bbac3bfcbd7b3d11d52c284f90d659b5e2d36181 Mon Sep 17 00:00:00 2001 From: William Putra Intan <61998484+williamputraintan@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:27:36 +1100 Subject: [PATCH 05/10] Update create-pg-db.ts --- .../stateless/postgres_manager/function/create-pg-db.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/workload/stateless/postgres_manager/function/create-pg-db.ts b/lib/workload/stateless/postgres_manager/function/create-pg-db.ts index 1a51abbf5..3f2eba0b7 100644 --- a/lib/workload/stateless/postgres_manager/function/create-pg-db.ts +++ b/lib/workload/stateless/postgres_manager/function/create-pg-db.ts @@ -23,7 +23,7 @@ export const handler = async (event: EventType) => { // restrict privileged access console.info('restrict database access from public'); - const restrictPrivilegedQuery = `REVOKE connect ON DATABASE ${microserviceName} FROM PUBLIC;`; + const restrictPrivilegedQuery = `REVOKE ALL ON DATABASE ${microserviceName} FROM PUBLIC;`; await executeSqlWithLog(client, restrictPrivilegedQuery); await client.end(); From a98c4d83a72e33d3644d5507cf58587330688629 Mon Sep 17 00:00:00 2001 From: William Putra Intan <61998484+williamputraintan@users.noreply.github.com> Date: Tue, 27 Feb 2024 18:34:35 +1100 Subject: [PATCH 06/10] remove get secret extension --- .../construct/postgresManager.ts | 10 --- .../postgres_manager/function/utils.ts | 31 ++++---- .../stateless/postgres_manager/package.json | 1 - .../stateless/postgres_manager/yarn.lock | 79 ------------------- 4 files changed, 14 insertions(+), 107 deletions(-) diff --git a/lib/workload/stateless/postgres_manager/construct/postgresManager.ts b/lib/workload/stateless/postgres_manager/construct/postgresManager.ts index 924da4863..3f555214d 100644 --- a/lib/workload/stateless/postgres_manager/construct/postgresManager.ts +++ b/lib/workload/stateless/postgres_manager/construct/postgresManager.ts @@ -38,20 +38,10 @@ export class PostgresManager extends Construct { '/orcabus/db-cluster-resource-id' ); - // Let lambda access secret manager via aws managed lambda extension - // Ref: - // https://aws.amazon.com/blogs/compute/using-the-aws-parameter-and-secrets-lambda-extension-to-cache-parameters-and-secrets/ - const lambdaLayerGetSecretExtension = lambda.LayerVersion.fromLayerVersionArn( - this, - 'GetSecretExtensionLayer', - 'arn:aws:lambda:ap-southeast-2:665172237481:layer:AWS-Parameters-and-Secrets-Lambda-Extension-Arm64:11' - ); - const rdsLambdaProps = { timeout: Duration.minutes(5), depsLockFilePath: __dirname + '/../yarn.lock', handler: 'handler', - layers: [lambdaLayerGetSecretExtension], runtime: lambda.Runtime.NODEJS_20_X, architecture: lambda.Architecture.ARM_64, environment: { diff --git a/lib/workload/stateless/postgres_manager/function/utils.ts b/lib/workload/stateless/postgres_manager/function/utils.ts index 4ee565009..1110f11d8 100644 --- a/lib/workload/stateless/postgres_manager/function/utils.ts +++ b/lib/workload/stateless/postgres_manager/function/utils.ts @@ -1,5 +1,5 @@ -import axios from 'axios'; import { Client } from 'pg'; +import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'; /** * There are 2 ways of connecting from microservice to db @@ -70,7 +70,7 @@ export const getRdsMasterSecret = async () => { const rdsSecretName = process.env.RDS_SECRET_MANAGER_NAME; if (!rdsSecretName) throw new Error('No RDS master secret configure in the env variable'); - const rdsSecret = JSON.parse(await getSecretManagerWithLayerExtension(rdsSecretName)); + const rdsSecret = JSON.parse(await getSecretValue(rdsSecretName)); return { host: rdsSecret.host, @@ -81,20 +81,17 @@ export const getRdsMasterSecret = async () => { }; }; -/** - * Wrapper to use the default lambda layer extension to query secret manager - * Ref: https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_lambda.html - * @param secretManagerName The microserviceName of the secret or ARN - * @returns - */ -export const getSecretManagerWithLayerExtension = async (secretManagerName: string) => { - const apiUrl = `http://localhost:2773/secretsmanager/get?secretId=${encodeURIComponent( - secretManagerName - )}`; - const headers = { 'X-Aws-Parameters-Secrets-Token': process.env.AWS_SESSION_TOKEN }; - const res = await axios.get(apiUrl, { - headers: headers, - }); +export const getSecretValue = async (secretManagerName: string) => { + const client = new SecretsManagerClient(); + const response = await client.send( + new GetSecretValueCommand({ + SecretId: secretManagerName, + }) + ); + + if (response.SecretString) { + return response.SecretString; + } - return res.data.SecretString; + throw new Error('Failed to retrieve secret value'); }; diff --git a/lib/workload/stateless/postgres_manager/package.json b/lib/workload/stateless/postgres_manager/package.json index c8387c61b..e560452c9 100644 --- a/lib/workload/stateless/postgres_manager/package.json +++ b/lib/workload/stateless/postgres_manager/package.json @@ -3,7 +3,6 @@ "packageManager": "yarn@3.5.1", "dependencies": { "@aws-sdk/client-secrets-manager": "^3.515.0", - "axios": "^1.6.7", "generate-password": "^1.7.1", "pg": "^8.11.3" }, diff --git a/lib/workload/stateless/postgres_manager/yarn.lock b/lib/workload/stateless/postgres_manager/yarn.lock index 0ef2529e4..95a9773d5 100644 --- a/lib/workload/stateless/postgres_manager/yarn.lock +++ b/lib/workload/stateless/postgres_manager/yarn.lock @@ -1017,24 +1017,6 @@ __metadata: languageName: node linkType: hard -"asynckit@npm:^0.4.0": - version: 0.4.0 - resolution: "asynckit@npm:0.4.0" - checksum: 7b78c451df768adba04e2d02e63e2d0bf3b07adcd6e42b4cf665cb7ce899bedd344c69a1dcbce355b5f972d597b25aaa1c1742b52cffd9caccb22f348114f6be - languageName: node - linkType: hard - -"axios@npm:^1.6.7": - version: 1.6.7 - resolution: "axios@npm:1.6.7" - dependencies: - follow-redirects: ^1.15.4 - form-data: ^4.0.0 - proxy-from-env: ^1.1.0 - checksum: 87d4d429927d09942771f3b3a6c13580c183e31d7be0ee12f09be6d5655304996bb033d85e54be81606f4e89684df43be7bf52d14becb73a12727bf33298a082 - languageName: node - linkType: hard - "bowser@npm:^2.11.0": version: 2.11.0 resolution: "bowser@npm:2.11.0" @@ -1049,22 +1031,6 @@ __metadata: languageName: node linkType: hard -"combined-stream@npm:^1.0.8": - version: 1.0.8 - resolution: "combined-stream@npm:1.0.8" - dependencies: - delayed-stream: ~1.0.0 - checksum: 49fa4aeb4916567e33ea81d088f6584749fc90c7abec76fd516bf1c5aa5c79f3584b5ba3de6b86d26ddd64bae5329c4c7479343250cfe71c75bb366eae53bb7c - languageName: node - linkType: hard - -"delayed-stream@npm:~1.0.0": - version: 1.0.0 - resolution: "delayed-stream@npm:1.0.0" - checksum: 46fe6e83e2cb1d85ba50bd52803c68be9bd953282fa7096f51fc29edd5d67ff84ff753c51966061e5ba7cb5e47ef6d36a91924eddb7f3f3483b1c560f77a0020 - languageName: node - linkType: hard - "fast-xml-parser@npm:4.2.5": version: 4.2.5 resolution: "fast-xml-parser@npm:4.2.5" @@ -1076,27 +1042,6 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.15.4": - version: 1.15.5 - resolution: "follow-redirects@npm:1.15.5" - peerDependenciesMeta: - debug: - optional: true - checksum: 5ca49b5ce6f44338cbfc3546823357e7a70813cecc9b7b768158a1d32c1e62e7407c944402a918ea8c38ae2e78266312d617dc68783fac502cbb55e1047b34ec - languageName: node - linkType: hard - -"form-data@npm:^4.0.0": - version: 4.0.0 - resolution: "form-data@npm:4.0.0" - dependencies: - asynckit: ^0.4.0 - combined-stream: ^1.0.8 - mime-types: ^2.1.12 - checksum: 01135bf8675f9d5c61ff18e2e2932f719ca4de964e3be90ef4c36aacfc7b9cb2fceb5eca0b7e0190e3383fe51c5b37f4cb80b62ca06a99aaabfcfd6ac7c9328c - languageName: node - linkType: hard - "generate-password@npm:^1.7.1": version: 1.7.1 resolution: "generate-password@npm:1.7.1" @@ -1111,29 +1056,12 @@ __metadata: "@aws-sdk/client-secrets-manager": ^3.515.0 "@types/aws-lambda": ^8.10.134 "@types/pg": ^8 - axios: ^1.6.7 generate-password: ^1.7.1 pg: ^8.11.3 typescript: ^5.3.3 languageName: unknown linkType: soft -"mime-db@npm:1.52.0": - version: 1.52.0 - resolution: "mime-db@npm:1.52.0" - checksum: 0d99a03585f8b39d68182803b12ac601d9c01abfa28ec56204fa330bc9f3d1c5e14beb049bafadb3dbdf646dfb94b87e24d4ec7b31b7279ef906a8ea9b6a513f - languageName: node - linkType: hard - -"mime-types@npm:^2.1.12": - version: 2.1.35 - resolution: "mime-types@npm:2.1.35" - dependencies: - mime-db: 1.52.0 - checksum: 89a5b7f1def9f3af5dad6496c5ed50191ae4331cc5389d7c521c8ad28d5fdad2d06fd81baf38fed813dc4e46bb55c8145bb0ff406330818c9cf712fb2e9b3836 - languageName: node - linkType: hard - "obuf@npm:~1.1.2": version: 1.1.2 resolution: "obuf@npm:1.1.2" @@ -1320,13 +1248,6 @@ __metadata: languageName: node linkType: hard -"proxy-from-env@npm:^1.1.0": - version: 1.1.0 - resolution: "proxy-from-env@npm:1.1.0" - checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4 - languageName: node - linkType: hard - "split2@npm:^4.1.0": version: 4.2.0 resolution: "split2@npm:4.2.0" From 492396e941ba8ad817b19930ab05fd15c9361a99 Mon Sep 17 00:00:00 2001 From: William Putra Intan <61998484+williamputraintan@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:07:35 +1100 Subject: [PATCH 07/10] feedback --- .../construct/postgresManager.ts | 7 +++- .../function/alter-pg-db-owner.ts | 8 ++--- .../postgres_manager/function/create-pg-db.ts | 10 +++--- .../function/create-pg-iam-role.ts | 8 ++--- .../function/create-pg-login-role.ts | 34 +++++++++++++------ .../stateless/postgres_manager/package.json | 1 - .../stateless/postgres_manager/yarn.lock | 8 ----- 7 files changed, 43 insertions(+), 33 deletions(-) diff --git a/lib/workload/stateless/postgres_manager/construct/postgresManager.ts b/lib/workload/stateless/postgres_manager/construct/postgresManager.ts index 3f555214d..e41f07c0e 100644 --- a/lib/workload/stateless/postgres_manager/construct/postgresManager.ts +++ b/lib/workload/stateless/postgres_manager/construct/postgresManager.ts @@ -35,7 +35,7 @@ export class PostgresManager extends Construct { const dbClusterResourceId = ssm.StringParameter.valueFromLookup( this, - '/orcabus/db-cluster-resource-id' + props.clusterResourceIdParameterName ); const rdsLambdaProps = { @@ -100,6 +100,11 @@ export class PostgresManager extends Construct { effect: iam.Effect.ALLOW, resources: ['arn:aws:secretsmanager:ap-southeast-2:*:secret:*'], }), + new iam.PolicyStatement({ + actions: ['secretsmanager:GetRandomPassword'], + effect: iam.Effect.ALLOW, + resources: ['*'], + }), ], entry: __dirname + '/../function/create-pg-login-role.ts', functionName: 'orcabus-create-pg-login-role', diff --git a/lib/workload/stateless/postgres_manager/function/alter-pg-db-owner.ts b/lib/workload/stateless/postgres_manager/function/alter-pg-db-owner.ts index 4d21b213a..0650ae06e 100644 --- a/lib/workload/stateless/postgres_manager/function/alter-pg-db-owner.ts +++ b/lib/workload/stateless/postgres_manager/function/alter-pg-db-owner.ts @@ -12,14 +12,14 @@ export const handler = async (event: EventType) => { const microserviceName = getMicroserviceName(microserviceConfig, event); const pgMasterConfig = await getRdsMasterSecret(); - const client = new Client(pgMasterConfig); - await client.connect(); + const pgClient = new Client(pgMasterConfig); + await pgClient.connect(); console.info('connected to RDS with master credential'); // assign db to this role to their own db console.info('alter database to be owned by this new role'); const alterDbRoleQuery = `ALTER DATABASE ${microserviceName} OWNER TO ${microserviceName}`; - await executeSqlWithLog(client, alterDbRoleQuery); + await executeSqlWithLog(pgClient, alterDbRoleQuery); - await client.end(); + await pgClient.end(); }; diff --git a/lib/workload/stateless/postgres_manager/function/create-pg-db.ts b/lib/workload/stateless/postgres_manager/function/create-pg-db.ts index 3f2eba0b7..32509b1e7 100644 --- a/lib/workload/stateless/postgres_manager/function/create-pg-db.ts +++ b/lib/workload/stateless/postgres_manager/function/create-pg-db.ts @@ -12,19 +12,19 @@ export const handler = async (event: EventType) => { const microserviceName = getMicroserviceName(microserviceConfig, event); const pgMasterConfig = await getRdsMasterSecret(); - const client = new Client(pgMasterConfig); - await client.connect(); + const pgClient = new Client(pgMasterConfig); + await pgClient.connect(); console.info('connected to RDS with master credential'); // create microservice db console.info('create a new database for the given microservice microserviceName'); const createDbQuery = `CREATE DATABASE ${microserviceName};`; - await executeSqlWithLog(client, createDbQuery); + await executeSqlWithLog(pgClient, createDbQuery); // restrict privileged access console.info('restrict database access from public'); const restrictPrivilegedQuery = `REVOKE ALL ON DATABASE ${microserviceName} FROM PUBLIC;`; - await executeSqlWithLog(client, restrictPrivilegedQuery); + await executeSqlWithLog(pgClient, restrictPrivilegedQuery); - await client.end(); + await pgClient.end(); }; diff --git a/lib/workload/stateless/postgres_manager/function/create-pg-iam-role.ts b/lib/workload/stateless/postgres_manager/function/create-pg-iam-role.ts index f9a150ec1..529cf6ef7 100644 --- a/lib/workload/stateless/postgres_manager/function/create-pg-iam-role.ts +++ b/lib/workload/stateless/postgres_manager/function/create-pg-iam-role.ts @@ -18,14 +18,14 @@ export const handler = async (event: EventType) => { throw new Error('this microservice is not configured for rds_iam'); } - const client = new Client(pgMasterConfig); - await client.connect(); + const pgClient = new Client(pgMasterConfig); + await pgClient.connect(); console.info('connected to RDS with master credential'); // create a new role console.info('create new user that has the rds_iam role'); const assignRdsIamQuery = `CREATE USER ${microserviceName}; GRANT rds_iam TO ${microserviceName};`; - await executeSqlWithLog(client, assignRdsIamQuery); + await executeSqlWithLog(pgClient, assignRdsIamQuery); - await client.end(); + await pgClient.end(); }; diff --git a/lib/workload/stateless/postgres_manager/function/create-pg-login-role.ts b/lib/workload/stateless/postgres_manager/function/create-pg-login-role.ts index e9510f58b..68bd1116c 100644 --- a/lib/workload/stateless/postgres_manager/function/create-pg-login-role.ts +++ b/lib/workload/stateless/postgres_manager/function/create-pg-login-role.ts @@ -1,6 +1,4 @@ -import { generate as pass_generator } from 'generate-password'; import { - executeSqlWithLog, getMicroserviceName, getMicroserviceConfig, getRdsMasterSecret, @@ -10,6 +8,8 @@ import { SecretsManagerClient, CreateSecretCommandInput, CreateSecretCommand, + GetRandomPasswordCommandInput, + GetRandomPasswordCommand, } from '@aws-sdk/client-secrets-manager'; import { Client } from 'pg'; @@ -18,6 +18,7 @@ type EventType = { }; export const handler = async (event: EventType) => { + const smClient = new SecretsManagerClient(); const microserviceConfig = getMicroserviceConfig(); const microserviceName = getMicroserviceName(microserviceConfig, event); const pgMasterConfig = await getRdsMasterSecret(); @@ -27,17 +28,31 @@ export const handler = async (event: EventType) => { throw new Error('this microservice is not configured for username-password authentication'); } - const client = new Client(pgMasterConfig); - await client.connect(); + const pgClient = new Client(pgMasterConfig); + await pgClient.connect(); console.info('connected to RDS with master credential'); // create a new role console.info('creating a user-password login role'); - const password = pass_generator({ length: 10, numbers: true }); - const createRoleQueryTemplate = `CREATE ROLE ${microserviceName} with LOGIN ENCRYPTED PASSWORD '${password}'`; - await executeSqlWithLog(client, createRoleQueryTemplate); - await client.end(); + // get random password using aws secret manager sdk + const randomPassConfig: GetRandomPasswordCommandInput = { + PasswordLength: 32, + ExcludePunctuation: true, + RequireEachIncludedType: true, + }; + const randomPassCommandInput = new GetRandomPasswordCommand(randomPassConfig); + const randomPassResponse = await smClient.send(randomPassCommandInput); + const password = randomPassResponse.RandomPassword; + if (!password) throw new Error('No password output from password generator'); + + // run the create role + const createRolePasswordSQL = `CREATE ROLE ${microserviceName} with LOGIN ENCRYPTED PASSWORD '${password}'`; + console.info( + `RESULT: ${JSON.stringify(await pgClient.query(createRolePasswordSQL), undefined, 2)}` + ); + + await pgClient.end(); // store the new db config at secret manager const secretValue = createSecretValue({ @@ -48,7 +63,6 @@ export const handler = async (event: EventType) => { dbname: microserviceName, }); - const smClient = new SecretsManagerClient(); const smInput: CreateSecretCommandInput = { Name: `orcabus/microservice/${microserviceName}`, Description: `orcabus microservice secret for '${microserviceName}'`, @@ -65,7 +79,7 @@ export const handler = async (event: EventType) => { `storing the role credential at the secret manager (orcabus/microservice/${microserviceName})` ); const response = await smClient.send(smCommand); - console.info(`ssm-response: ${response}`); + console.info(`ssm-response: ${JSON.stringify(response, undefined, 2)}`); }; /** diff --git a/lib/workload/stateless/postgres_manager/package.json b/lib/workload/stateless/postgres_manager/package.json index e560452c9..0e70c058a 100644 --- a/lib/workload/stateless/postgres_manager/package.json +++ b/lib/workload/stateless/postgres_manager/package.json @@ -3,7 +3,6 @@ "packageManager": "yarn@3.5.1", "dependencies": { "@aws-sdk/client-secrets-manager": "^3.515.0", - "generate-password": "^1.7.1", "pg": "^8.11.3" }, "devDependencies": { diff --git a/lib/workload/stateless/postgres_manager/yarn.lock b/lib/workload/stateless/postgres_manager/yarn.lock index 95a9773d5..a47f9a207 100644 --- a/lib/workload/stateless/postgres_manager/yarn.lock +++ b/lib/workload/stateless/postgres_manager/yarn.lock @@ -1042,13 +1042,6 @@ __metadata: languageName: node linkType: hard -"generate-password@npm:^1.7.1": - version: 1.7.1 - resolution: "generate-password@npm:1.7.1" - checksum: 76ab72dcf13ae174e4e069b32b94d7c2613c6e83db1c00ffad17a480a80875b28c4ed8f0767b143f7103b0742afb45a10549e6ef2f72c0a994979e77b8e95b86 - languageName: node - linkType: hard - "lambda-with-rds@workspace:.": version: 0.0.0-use.local resolution: "lambda-with-rds@workspace:." @@ -1056,7 +1049,6 @@ __metadata: "@aws-sdk/client-secrets-manager": ^3.515.0 "@types/aws-lambda": ^8.10.134 "@types/pg": ^8 - generate-password: ^1.7.1 pg: ^8.11.3 typescript: ^5.3.3 languageName: unknown From bfe2611890dc617fa29b14b470702ae9ee3842a7 Mon Sep 17 00:00:00 2001 From: William Putra Intan <61998484+williamputraintan@users.noreply.github.com> Date: Wed, 28 Feb 2024 18:20:51 +1100 Subject: [PATCH 08/10] Update prbuild.yml --- .github/workflows/prbuild.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prbuild.yml b/.github/workflows/prbuild.yml index 4d8a55e7f..a09eca956 100644 --- a/.github/workflows/prbuild.yml +++ b/.github/workflows/prbuild.yml @@ -31,7 +31,7 @@ jobs: # TODO see whether we can leverage https://github.com/pre-commit/action - name: Install system-wide tools dependencies run: | - pip3 install pre-commit detect-secrets black ggshield awscli-local + pip3 install pre-commit detect-secrets black ggshield - name: Checkout code uses: actions/checkout@v3 From 7e9f8878c2be5c3e3a9c5d64eab32f945fc8a80e Mon Sep 17 00:00:00 2001 From: William Putra Intan <61998484+williamputraintan@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:50:08 +1100 Subject: [PATCH 09/10] add `cdk-nag` supression --- .../construct/postgresManager.ts | 4 +++- test/stateless/stateless-deployment.test.ts | 20 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/workload/stateless/postgres_manager/construct/postgresManager.ts b/lib/workload/stateless/postgres_manager/construct/postgresManager.ts index e41f07c0e..8dd9a33e9 100644 --- a/lib/workload/stateless/postgres_manager/construct/postgresManager.ts +++ b/lib/workload/stateless/postgres_manager/construct/postgresManager.ts @@ -98,7 +98,9 @@ export class PostgresManager extends Construct { new iam.PolicyStatement({ actions: ['secretsmanager:CreateSecret', 'secretsmanager:TagResource'], effect: iam.Effect.ALLOW, - resources: ['arn:aws:secretsmanager:ap-southeast-2:*:secret:*'], + resources: [ + `arn:aws:secretsmanager:ap-southeast-2:${process.env.CDK_DEFAULT_ACCOUNT}:secret:*`, + ], }), new iam.PolicyStatement({ actions: ['secretsmanager:GetRandomPassword'], diff --git a/test/stateless/stateless-deployment.test.ts b/test/stateless/stateless-deployment.test.ts index 5f222c846..a213e4648 100644 --- a/test/stateless/stateless-deployment.test.ts +++ b/test/stateless/stateless-deployment.test.ts @@ -1,7 +1,7 @@ import { App, Aspects } from 'aws-cdk-lib'; import { Annotations, Match } from 'aws-cdk-lib/assertions'; import { SynthesisMessage } from 'aws-cdk-lib/cx-api'; -import { AwsSolutionsChecks } from 'cdk-nag'; +import { AwsSolutionsChecks, NagSuppressions } from 'cdk-nag'; import { OrcaBusStatelessStack } from '../../lib/workload/orcabus-stateless-stack'; import { getEnvironmentConfig } from '../../config/constants'; @@ -26,8 +26,22 @@ describe('cdk-nag-stateless-stack', () => { }); Aspects.of(stack).add(new AwsSolutionsChecks()); - // Suppressions (if any) - // ... + NagSuppressions.addStackSuppressions(stack, [ + { id: 'AwsSolutions-IAM4', reason: 'allow to use AWS managed policy' }, + ]); + + // suppress by resource + NagSuppressions.addResourceSuppressionsByPath( + stack, + `/TestStack/PostgresManager/CreateUserPassPostgresLambda/ServiceRole/DefaultPolicy/Resource`, + [ + { + id: 'AwsSolutions-IAM5', + reason: + "'*' is required for secretsmanager:GetRandomPassword and new SM ARN will contain random character", + }, + ] + ); }); test('cdk-nag AwsSolutions Pack errors', () => { From 9f8e04f2a21c1514808c181fc3bf622a0fc4321b Mon Sep 17 00:00:00 2001 From: William Putra Intan <61998484+williamputraintan@users.noreply.github.com> Date: Thu, 29 Feb 2024 11:10:43 +1100 Subject: [PATCH 10/10] Update tsconfig.json --- tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 5f993b327..1dc61d483 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,6 +30,7 @@ "exclude": [ "node_modules", "cdk.out", - "lib/workload/stateless/metadata_manager" + "lib/workload/stateless/metadata_manager", + "lib/workload/stateless/postgres_manager" ] }