@digitraffic/common 2023.1.18-2 → 2023.1.23-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aws/infra/api/integration.d.ts +2 -1
- package/dist/aws/infra/api/integration.js +3 -2
- package/dist/aws/infra/api/response.d.ts +2 -1
- package/dist/aws/infra/api/response.js +13 -7
- package/dist/aws/infra/canaries/canary-alarm.js +11 -13
- package/dist/aws/infra/canaries/canary.js +2 -4
- package/dist/aws/infra/canaries/database-checker.js +4 -1
- package/dist/aws/infra/canaries/url-canary.js +1 -0
- package/dist/aws/infra/canaries/url-checker.d.ts +2 -2
- package/dist/aws/infra/canaries/url-checker.js +24 -5
- package/dist/aws/infra/sqs-integration.d.ts +1 -3
- package/dist/aws/infra/sqs-integration.js +28 -32
- package/dist/aws/infra/sqs-queue.d.ts +0 -2
- package/dist/aws/infra/sqs-queue.js +31 -24
- package/dist/aws/infra/stack/lambda-configs.d.ts +2 -31
- package/dist/aws/infra/stack/lambda-configs.js +5 -38
- package/dist/aws/infra/stack/monitoredfunction.js +3 -1
- package/dist/aws/infra/stacks/db-stack.js +1 -1
- package/dist/aws/infra/stacks/network-stack.d.ts +2 -1
- package/dist/aws/infra/stacks/network-stack.js +4 -2
- package/dist/aws/runtime/digitraffic-integration-response.d.ts +2 -2
- package/dist/aws/runtime/digitraffic-integration-response.js +6 -4
- package/dist/aws/runtime/secrets/dbsecret.d.ts +0 -39
- package/dist/aws/runtime/secrets/dbsecret.js +1 -71
- package/dist/aws/runtime/secrets/proxy-holder.js +5 -4
- package/dist/aws/runtime/secrets/rds-holder.js +5 -4
- package/dist/aws/runtime/secrets/secret-holder.d.ts +0 -4
- package/dist/aws/runtime/secrets/secret-holder.js +6 -12
- package/dist/aws/runtime/secrets/secret.d.ts +0 -6
- package/dist/aws/runtime/secrets/secret.js +8 -17
- package/dist/database/database.d.ts +7 -0
- package/dist/database/database.js +19 -8
- package/dist/test/db-testutils.js +4 -5
- package/package.json +1 -1
- package/src/aws/infra/api/integration.ts +8 -3
- package/src/aws/infra/api/response.ts +16 -16
- package/src/aws/infra/canaries/canary-alarm.ts +26 -24
- package/src/aws/infra/canaries/canary.ts +2 -4
- package/src/aws/infra/canaries/database-checker.ts +4 -1
- package/src/aws/infra/canaries/url-canary.ts +2 -1
- package/src/aws/infra/canaries/url-checker.ts +28 -11
- package/src/aws/infra/sqs-integration.ts +51 -47
- package/src/aws/infra/sqs-queue.ts +85 -53
- package/src/aws/infra/stack/lambda-configs.ts +6 -69
- package/src/aws/infra/stack/monitoredfunction.ts +2 -1
- package/src/aws/infra/stacks/db-stack.ts +1 -1
- package/src/aws/infra/stacks/network-stack.ts +7 -3
- package/src/aws/runtime/digitraffic-integration-response.ts +16 -9
- package/src/aws/runtime/secrets/dbsecret.ts +1 -117
- package/src/aws/runtime/secrets/proxy-holder.ts +2 -5
- package/src/aws/runtime/secrets/rds-holder.ts +2 -1
- package/src/aws/runtime/secrets/secret-holder.ts +8 -20
- package/src/aws/runtime/secrets/secret.ts +17 -22
- package/src/database/database.ts +14 -3
- package/src/test/db-testutils.ts +5 -2
- package/dist/test/secret.d.ts +0 -3
- package/dist/test/secret.js +0 -25
- package/src/test/secret.ts +0 -23
@@ -1,18 +1,20 @@
|
|
1
|
-
import {Queue, QueueEncryption, QueueProps} from "aws-cdk-lib/aws-sqs";
|
2
|
-
import {Duration} from "aws-cdk-lib";
|
3
|
-
import {BlockPublicAccess, Bucket} from "aws-cdk-lib/aws-s3";
|
4
|
-
import {PolicyStatement} from "aws-cdk-lib/aws-iam";
|
5
|
-
import {InlineCode, Runtime} from "aws-cdk-lib/aws-lambda";
|
6
|
-
import {RetentionDays} from "aws-cdk-lib/aws-logs";
|
7
|
-
import {SqsEventSource} from "aws-cdk-lib/aws-lambda-event-sources";
|
8
|
-
import {
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
import {
|
13
|
-
import {
|
14
|
-
import {
|
15
|
-
import {
|
1
|
+
import { Queue, QueueEncryption, QueueProps } from "aws-cdk-lib/aws-sqs";
|
2
|
+
import { Duration } from "aws-cdk-lib";
|
3
|
+
import { BlockPublicAccess, Bucket } from "aws-cdk-lib/aws-s3";
|
4
|
+
import { PolicyStatement } from "aws-cdk-lib/aws-iam";
|
5
|
+
import { InlineCode, Runtime } from "aws-cdk-lib/aws-lambda";
|
6
|
+
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
7
|
+
import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
|
8
|
+
import {
|
9
|
+
ComparisonOperator,
|
10
|
+
TreatMissingData,
|
11
|
+
} from "aws-cdk-lib/aws-cloudwatch";
|
12
|
+
import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
|
13
|
+
import { ManagedUpload } from "aws-sdk/clients/s3";
|
14
|
+
import { S3 } from "aws-sdk";
|
15
|
+
import { SQSEvent, SQSHandler, SQSRecord } from "aws-lambda";
|
16
|
+
import { DigitrafficStack } from "./stack/stack";
|
17
|
+
import { MonitoredFunction } from "./stack/monitoredfunction";
|
16
18
|
|
17
19
|
/**
|
18
20
|
* Construct for creating SQS-queues.
|
@@ -21,20 +23,23 @@ import {MonitoredFunction} from "./stack/monitoredfunction";
|
|
21
23
|
* and an alarm for the queue. Anything that goes to the dlq will be written into the bucket and the alarm is activated.
|
22
24
|
*/
|
23
25
|
export class DigitrafficSqsQueue extends Queue {
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
static create(
|
27
|
+
stack: DigitrafficStack,
|
28
|
+
name: string,
|
29
|
+
props: QueueProps
|
30
|
+
): DigitrafficSqsQueue {
|
29
31
|
const queueName = `${stack.configuration.shortName}-${name}-Queue`;
|
30
|
-
const queueProps = {
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
const queueProps = {
|
33
|
+
...props,
|
34
|
+
...{
|
35
|
+
encryption: QueueEncryption.KMS_MANAGED,
|
36
|
+
queueName,
|
37
|
+
deadLetterQueue: props.deadLetterQueue ?? {
|
38
|
+
maxReceiveCount: 2,
|
39
|
+
queue: DigitrafficDLQueue.create(stack, name),
|
40
|
+
},
|
36
41
|
},
|
37
|
-
}
|
42
|
+
};
|
38
43
|
|
39
44
|
return new DigitrafficSqsQueue(stack, queueName, queueProps);
|
40
45
|
}
|
@@ -61,15 +66,15 @@ export class DigitrafficDLQueue {
|
|
61
66
|
functionName: dlqFunctionName,
|
62
67
|
code: getDlqCode(dlqBucket.bucketName),
|
63
68
|
timeout: Duration.seconds(10),
|
64
|
-
handler:
|
69
|
+
handler: "index.handler",
|
65
70
|
memorySize: 128,
|
66
71
|
reservedConcurrentExecutions: 1,
|
67
72
|
});
|
68
73
|
|
69
74
|
const statement = new PolicyStatement();
|
70
|
-
statement.addActions(
|
71
|
-
statement.addActions(
|
72
|
-
statement.addResources(dlqBucket.bucketArn +
|
75
|
+
statement.addActions("s3:PutObject");
|
76
|
+
statement.addActions("s3:PutObjectAcl");
|
77
|
+
statement.addResources(dlqBucket.bucketArn + "/*");
|
73
78
|
|
74
79
|
lambda.addToRolePolicy(statement);
|
75
80
|
lambda.addEventSource(new SqsEventSource(dlq));
|
@@ -84,18 +89,19 @@ function addDLQAlarm(stack: DigitrafficStack, dlqName: string, dlq: Queue) {
|
|
84
89
|
const alarmName = `${dlqName}-Alarm`;
|
85
90
|
dlq.metricNumberOfMessagesReceived({
|
86
91
|
period: Duration.minutes(5),
|
87
|
-
})
|
88
|
-
alarmName,
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
92
|
+
})
|
93
|
+
.createAlarm(stack, alarmName, {
|
94
|
+
alarmName,
|
95
|
+
threshold: 0,
|
96
|
+
evaluationPeriods: 1,
|
97
|
+
treatMissingData: TreatMissingData.NOT_BREACHING,
|
98
|
+
comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
|
99
|
+
})
|
100
|
+
.addAlarmAction(new SnsAction(stack.warningTopic));
|
94
101
|
}
|
95
102
|
|
96
103
|
function getDlqCode(bName: string): InlineCode {
|
97
|
-
const functionBody = DLQ_LAMBDA_CODE
|
98
|
-
.replace("__bucketName__", bName)
|
104
|
+
const functionBody = DLQ_LAMBDA_CODE.replace("__bucketName__", bName)
|
99
105
|
.replace("__upload__", uploadToS3.toString())
|
100
106
|
.replace("__doUpload__", doUpload.toString())
|
101
107
|
.replace("__handler__", createHandler().toString().substring(23)); // remove function handler() from signature
|
@@ -103,38 +109,64 @@ function getDlqCode(bName: string): InlineCode {
|
|
103
109
|
return new InlineCode(functionBody);
|
104
110
|
}
|
105
111
|
|
106
|
-
async function uploadToS3(
|
112
|
+
async function uploadToS3(
|
113
|
+
s3: S3,
|
114
|
+
bName: string,
|
115
|
+
body: string,
|
116
|
+
objectName: string
|
117
|
+
): Promise<void> {
|
107
118
|
try {
|
108
|
-
console.info(
|
119
|
+
console.info("writing %s to %s", objectName, bName);
|
109
120
|
await doUpload(s3, bName, body, objectName);
|
110
121
|
} catch (error) {
|
111
122
|
console.warn(error);
|
112
|
-
console.warn(
|
123
|
+
console.warn("method=uploadToS3 retrying upload to bucket %s", bName);
|
113
124
|
try {
|
114
125
|
await doUpload(s3, bName, body, objectName);
|
115
126
|
} catch (e2) {
|
116
|
-
console.error(
|
127
|
+
console.error(
|
128
|
+
"method=uploadToS3 failed retrying upload to bucket %s",
|
129
|
+
bName
|
130
|
+
);
|
117
131
|
}
|
118
132
|
}
|
119
133
|
}
|
120
134
|
|
121
|
-
function doUpload(
|
122
|
-
|
123
|
-
|
124
|
-
|
135
|
+
function doUpload(
|
136
|
+
s3: S3,
|
137
|
+
bName: string,
|
138
|
+
Body: string,
|
139
|
+
Key: string
|
140
|
+
): Promise<ManagedUpload.SendData> {
|
141
|
+
return s3
|
142
|
+
.upload({
|
143
|
+
Bucket: bName,
|
144
|
+
Body,
|
145
|
+
Key,
|
146
|
+
})
|
147
|
+
.promise();
|
125
148
|
}
|
126
149
|
|
127
150
|
// bucketName is unused, will be overridden in the actual lambda code below
|
128
|
-
const bucketName =
|
151
|
+
const bucketName = "";
|
129
152
|
|
130
153
|
function createHandler(): SQSHandler {
|
131
154
|
return async function handler(event: SQSEvent): Promise<void> {
|
132
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
133
|
-
const AWS = require(
|
155
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment
|
156
|
+
const AWS = require("aws-sdk");
|
134
157
|
|
135
158
|
const millis = new Date().getTime();
|
136
|
-
await Promise.all(
|
137
|
-
|
159
|
+
await Promise.all(
|
160
|
+
event.Records.map((e: SQSRecord, idx: number) =>
|
161
|
+
uploadToS3(
|
162
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
163
|
+
new AWS.S3(),
|
164
|
+
bucketName,
|
165
|
+
e.body,
|
166
|
+
`dlq-${millis}-${idx}.json`
|
167
|
+
)
|
168
|
+
)
|
169
|
+
);
|
138
170
|
};
|
139
171
|
}
|
140
172
|
|
@@ -6,7 +6,7 @@ import {
|
|
6
6
|
Runtime,
|
7
7
|
} from "aws-cdk-lib/aws-lambda";
|
8
8
|
import { Duration } from "aws-cdk-lib";
|
9
|
-
import {
|
9
|
+
import { IVpc, SubnetSelection } from "aws-cdk-lib/aws-ec2";
|
10
10
|
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
11
11
|
import { Role } from "aws-cdk-lib/aws-iam";
|
12
12
|
import { DigitrafficStack } from "./stack";
|
@@ -19,26 +19,6 @@ export type DBLambdaEnvironment = LambdaEnvironment & {
|
|
19
19
|
DB_APPLICATION: string;
|
20
20
|
};
|
21
21
|
|
22
|
-
export interface LambdaConfiguration {
|
23
|
-
vpcId: string;
|
24
|
-
allowFromIpAddresses?: string[];
|
25
|
-
privateSubnetIds: string[];
|
26
|
-
availabilityZones: string[];
|
27
|
-
lambdaDbSgId: string;
|
28
|
-
dbProps?: DbProps;
|
29
|
-
defaultLambdaDurationSeconds?: number;
|
30
|
-
logsDestinationArn: string;
|
31
|
-
memorySize?: number;
|
32
|
-
runtime?: Runtime;
|
33
|
-
}
|
34
|
-
|
35
|
-
declare interface DbProps {
|
36
|
-
username: string;
|
37
|
-
password: string;
|
38
|
-
uri?: string;
|
39
|
-
ro_uri?: string;
|
40
|
-
}
|
41
|
-
|
42
22
|
export function databaseFunctionProps(
|
43
23
|
stack: DigitrafficStack,
|
44
24
|
environment: LambdaEnvironment,
|
@@ -61,9 +41,9 @@ export function databaseFunctionProps(
|
|
61
41
|
config
|
62
42
|
),
|
63
43
|
...{
|
64
|
-
vpc: stack.vpc
|
44
|
+
vpc: stack.vpc ?? undefined,
|
65
45
|
vpcSubnets,
|
66
|
-
securityGroup: stack.lambdaDbSg
|
46
|
+
securityGroup: stack.lambdaDbSg ?? undefined,
|
67
47
|
},
|
68
48
|
};
|
69
49
|
}
|
@@ -101,47 +81,6 @@ function getAssetCode(
|
|
101
81
|
return new AssetCode(lambdaPath);
|
102
82
|
}
|
103
83
|
|
104
|
-
/**
|
105
|
-
* Creates a base configuration for a Lambda that uses an RDS database
|
106
|
-
* @param vpc "Private" Lambdas are associated with a VPC
|
107
|
-
* @param lambdaDbSg Security Group shared by Lambda and RDS
|
108
|
-
* @param props Database connection properties for the Lambda
|
109
|
-
* @param config Lambda configuration
|
110
|
-
*/
|
111
|
-
export function dbLambdaConfiguration(
|
112
|
-
vpc: IVpc,
|
113
|
-
lambdaDbSg: ISecurityGroup,
|
114
|
-
props: LambdaConfiguration,
|
115
|
-
config: FunctionParameters
|
116
|
-
): FunctionProps {
|
117
|
-
return {
|
118
|
-
runtime: props.runtime ?? Runtime.NODEJS_16_X,
|
119
|
-
memorySize: props.memorySize ?? config.memorySize ?? 1024,
|
120
|
-
functionName: config.functionName,
|
121
|
-
code: config.code,
|
122
|
-
role: config.role,
|
123
|
-
handler: config.handler,
|
124
|
-
timeout: Duration.seconds(
|
125
|
-
config.timeout ?? props.defaultLambdaDurationSeconds ?? 60
|
126
|
-
),
|
127
|
-
environment: config.environment ?? {
|
128
|
-
DB_USER: props.dbProps?.username ?? "",
|
129
|
-
DB_PASS: props.dbProps?.password ?? "",
|
130
|
-
DB_URI:
|
131
|
-
(config.readOnly
|
132
|
-
? props.dbProps?.ro_uri
|
133
|
-
: props.dbProps?.uri) ?? "",
|
134
|
-
},
|
135
|
-
logRetention: RetentionDays.ONE_YEAR,
|
136
|
-
vpc: vpc,
|
137
|
-
vpcSubnets: {
|
138
|
-
subnets: vpc.privateSubnets,
|
139
|
-
},
|
140
|
-
securityGroups: [lambdaDbSg],
|
141
|
-
reservedConcurrentExecutions: config.reservedConcurrentExecutions ?? 3,
|
142
|
-
};
|
143
|
-
}
|
144
|
-
|
145
84
|
export function defaultLambdaConfiguration(
|
146
85
|
config: FunctionParameters
|
147
86
|
): FunctionProps {
|
@@ -155,7 +94,7 @@ export function defaultLambdaConfiguration(
|
|
155
94
|
reservedConcurrentExecutions: config.reservedConcurrentExecutions,
|
156
95
|
code: config.code,
|
157
96
|
role: config.role,
|
158
|
-
timeout: Duration.seconds(config.timeout
|
97
|
+
timeout: Duration.seconds(config.timeout ?? 10),
|
159
98
|
};
|
160
99
|
if (config.vpc) {
|
161
100
|
return {
|
@@ -163,7 +102,7 @@ export function defaultLambdaConfiguration(
|
|
163
102
|
...{
|
164
103
|
vpc: config.vpc,
|
165
104
|
vpcSubnets: {
|
166
|
-
subnets: config.vpc
|
105
|
+
subnets: config.vpc.privateSubnets,
|
167
106
|
},
|
168
107
|
},
|
169
108
|
};
|
@@ -178,9 +117,7 @@ export interface FunctionParameters {
|
|
178
117
|
code: Code;
|
179
118
|
handler: string;
|
180
119
|
readOnly?: boolean;
|
181
|
-
environment?:
|
182
|
-
[key: string]: string;
|
183
|
-
};
|
120
|
+
environment?: Record<string, string>;
|
184
121
|
reservedConcurrentExecutions?: number;
|
185
122
|
role?: Role;
|
186
123
|
vpc?: IVpc;
|
@@ -82,7 +82,8 @@ export class MonitoredFunction extends Function {
|
|
82
82
|
stack.configuration.production
|
83
83
|
) {
|
84
84
|
throw new Error(
|
85
|
-
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
86
|
+
`Function ${functionProps.functionName!} has DISABLE_ALARMS. Remove before installing to production or define your own properties!`
|
86
87
|
);
|
87
88
|
}
|
88
89
|
|
@@ -139,7 +139,7 @@ export class DbStack extends Stack {
|
|
139
139
|
vpc,
|
140
140
|
securityGroups: [securityGroup],
|
141
141
|
vpcSubnets: {
|
142
|
-
subnetType: SubnetType.
|
142
|
+
subnetType: SubnetType.PRIVATE_WITH_EGRESS,
|
143
143
|
},
|
144
144
|
instanceType: configuration.dbInstanceType,
|
145
145
|
parameterGroup,
|
@@ -1,7 +1,8 @@
|
|
1
1
|
import { Stack } from "aws-cdk-lib";
|
2
2
|
import { Construct } from "constructs";
|
3
|
-
import { SubnetType, Vpc } from "aws-cdk-lib/aws-ec2";
|
3
|
+
import { IVpc, SubnetType, Vpc } from "aws-cdk-lib/aws-ec2";
|
4
4
|
import { InfraStackConfiguration } from "./intra-stack-configuration";
|
5
|
+
import { exportValue } from "../import-util";
|
5
6
|
|
6
7
|
export interface NetworkConfiguration {
|
7
8
|
readonly vpcName: string;
|
@@ -9,6 +10,8 @@ export interface NetworkConfiguration {
|
|
9
10
|
}
|
10
11
|
|
11
12
|
export class NetworkStack extends Stack {
|
13
|
+
readonly vpc: IVpc;
|
14
|
+
|
12
15
|
constructor(
|
13
16
|
scope: Construct,
|
14
17
|
id: string,
|
@@ -19,7 +22,8 @@ export class NetworkStack extends Stack {
|
|
19
22
|
env: isc.env,
|
20
23
|
});
|
21
24
|
|
22
|
-
this.createVpc(configuration);
|
25
|
+
this.vpc = this.createVpc(configuration);
|
26
|
+
exportValue(this, isc.environmentName, "VPCID", this.vpc.vpcId);
|
23
27
|
}
|
24
28
|
|
25
29
|
createVpc(configuration: NetworkConfiguration): Vpc {
|
@@ -38,7 +42,7 @@ export class NetworkStack extends Stack {
|
|
38
42
|
{
|
39
43
|
name: "private",
|
40
44
|
cidrMask: 24,
|
41
|
-
subnetType: SubnetType.
|
45
|
+
subnetType: SubnetType.PRIVATE_WITH_EGRESS,
|
42
46
|
},
|
43
47
|
],
|
44
48
|
});
|
@@ -1,11 +1,13 @@
|
|
1
|
-
import {IntegrationResponse} from "aws-cdk-lib/aws-apigateway";
|
2
|
-
import {MediaType} from "../types/mediatypes";
|
3
|
-
import {
|
1
|
+
import { IntegrationResponse } from "aws-cdk-lib/aws-apigateway";
|
2
|
+
import { MediaType } from "../types/mediatypes";
|
3
|
+
import {
|
4
|
+
getDeprecatedDefaultLambdaResponse,
|
5
|
+
RESPONSE_DEFAULT_LAMBDA,
|
6
|
+
} from "../infra/api/response";
|
4
7
|
|
5
8
|
export abstract class DigitrafficIntegrationResponse {
|
6
|
-
|
7
|
-
|
8
|
-
return this.create("200", mediaType);
|
9
|
+
static ok(mediaType: MediaType, sunset?: string): IntegrationResponse {
|
10
|
+
return this.create("200", mediaType, sunset);
|
9
11
|
}
|
10
12
|
|
11
13
|
static badRequest(mediaType?: MediaType): IntegrationResponse {
|
@@ -16,13 +18,18 @@ export abstract class DigitrafficIntegrationResponse {
|
|
16
18
|
return this.create("501", mediaType ?? MediaType.TEXT_PLAIN);
|
17
19
|
}
|
18
20
|
|
19
|
-
static create(
|
21
|
+
static create(
|
22
|
+
statusCode: string,
|
23
|
+
mediaType: MediaType,
|
24
|
+
sunset?: string
|
25
|
+
): IntegrationResponse {
|
20
26
|
return {
|
21
27
|
statusCode,
|
22
28
|
responseTemplates: {
|
23
|
-
[mediaType]:
|
29
|
+
[mediaType]: sunset
|
30
|
+
? getDeprecatedDefaultLambdaResponse(sunset)
|
31
|
+
: RESPONSE_DEFAULT_LAMBDA,
|
24
32
|
},
|
25
33
|
};
|
26
34
|
}
|
27
35
|
}
|
28
|
-
|
@@ -1,11 +1,4 @@
|
|
1
|
-
import { GenericSecret
|
2
|
-
|
3
|
-
export interface DbSecret {
|
4
|
-
readonly username: string;
|
5
|
-
readonly password: string;
|
6
|
-
readonly host: string;
|
7
|
-
readonly ro_host: string;
|
8
|
-
}
|
1
|
+
import { GenericSecret } from "./secret";
|
9
2
|
|
10
3
|
export enum RdsProxySecretKey {
|
11
4
|
username = "username",
|
@@ -24,115 +17,6 @@ export enum RdsSecretKey {
|
|
24
17
|
export type RdsProxySecret = Record<RdsProxySecretKey, string>;
|
25
18
|
export type RdsSecret = Record<RdsSecretKey, string>;
|
26
19
|
|
27
|
-
export enum DatabaseEnvironmentKeys {
|
28
|
-
DB_USER = "DB_USER",
|
29
|
-
DB_PASS = "DB_PASS",
|
30
|
-
DB_URI = "DB_URI",
|
31
|
-
DB_RO_URI = "DB_RO_URI",
|
32
|
-
DB_APPLICATION = "DB_APPLICATION",
|
33
|
-
}
|
34
|
-
|
35
|
-
function setDbSecret(secret: DbSecret) {
|
36
|
-
process.env[DatabaseEnvironmentKeys.DB_USER] = secret.username;
|
37
|
-
process.env[DatabaseEnvironmentKeys.DB_PASS] = secret.password;
|
38
|
-
process.env[DatabaseEnvironmentKeys.DB_URI] = secret.host;
|
39
|
-
process.env[DatabaseEnvironmentKeys.DB_RO_URI] = secret.ro_host;
|
40
|
-
}
|
41
|
-
|
42
|
-
// cached at Lambda container level
|
43
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
44
|
-
let cachedSecret: any;
|
45
|
-
|
46
|
-
const missingSecretErrorText = "Missing or empty secretId";
|
47
|
-
|
48
|
-
/**
|
49
|
-
* You can give the following options for retrieving a secret:
|
50
|
-
*
|
51
|
-
* expectedKeys: the list of keys the secret must include. If not, an error will be thrown.
|
52
|
-
* prefix: a prefix that's included in retrieved secret's keys. Only keys begining with the prefix will be included.
|
53
|
-
* The secret that is passed to the given function will not include the prefix in it's keys.
|
54
|
-
|
55
|
-
*/
|
56
|
-
export interface SecretOptions {
|
57
|
-
readonly expectedKeys?: string[];
|
58
|
-
readonly prefix?: string;
|
59
|
-
}
|
60
|
-
|
61
|
-
export type SecretToPromiseFunction<Secret, Response = void> = (
|
62
|
-
secret: Secret
|
63
|
-
) => Promise<Response> | void;
|
64
|
-
export type SecretFunction<Secret, Response = void> = (
|
65
|
-
secretId: string,
|
66
|
-
fn: SecretToPromiseFunction<Secret, Response>,
|
67
|
-
options?: SecretOptions
|
68
|
-
) => Promise<Response | void>;
|
69
|
-
export type EmptySecretFunction<Response = void> = SecretFunction<
|
70
|
-
DbSecret,
|
71
|
-
Response
|
72
|
-
>;
|
73
|
-
|
74
|
-
/**
|
75
|
-
* Run the given function with secret retrieved from Secrets Manager. Also injects database-credentials into environment.
|
76
|
-
*
|
77
|
-
* @deprecated use SecretHolder & ProxyHolder
|
78
|
-
* @see SecretOptions
|
79
|
-
*
|
80
|
-
* @param {string} secretId
|
81
|
-
* @param {function} fn
|
82
|
-
* @param {SecretOptions} options
|
83
|
-
*/
|
84
|
-
export async function withDbSecret<Secret, Response>(
|
85
|
-
secretId: string,
|
86
|
-
fn: SecretToPromiseFunction<Secret, Response>,
|
87
|
-
options?: SecretOptions
|
88
|
-
): Promise<Response | void> {
|
89
|
-
if (!secretId) {
|
90
|
-
console.error(missingSecretErrorText);
|
91
|
-
return Promise.reject(missingSecretErrorText);
|
92
|
-
}
|
93
|
-
|
94
|
-
if (!cachedSecret) {
|
95
|
-
// if prefix is given, first set db values and then fetch secret
|
96
|
-
if (options?.prefix) {
|
97
|
-
// first set db values
|
98
|
-
await withSecret(secretId, (fetchedSecret: DbSecret) => {
|
99
|
-
setDbSecret(fetchedSecret);
|
100
|
-
});
|
101
|
-
|
102
|
-
// then actual secret
|
103
|
-
await withSecretAndPrefix(
|
104
|
-
secretId,
|
105
|
-
options.prefix,
|
106
|
-
(fetchedSecret: Secret) => {
|
107
|
-
cachedSecret = fetchedSecret;
|
108
|
-
}
|
109
|
-
);
|
110
|
-
} else {
|
111
|
-
await withSecret(secretId, (fetchedSecret: DbSecret) => {
|
112
|
-
setDbSecret(fetchedSecret);
|
113
|
-
cachedSecret = fetchedSecret;
|
114
|
-
});
|
115
|
-
}
|
116
|
-
}
|
117
|
-
try {
|
118
|
-
if (options?.expectedKeys?.length) {
|
119
|
-
checkExpectedSecretKeys(options.expectedKeys, cachedSecret);
|
120
|
-
}
|
121
|
-
return fn(cachedSecret);
|
122
|
-
} catch (error) {
|
123
|
-
console.error(
|
124
|
-
"method=withDbSecret Caught an error, refreshing secret",
|
125
|
-
error
|
126
|
-
);
|
127
|
-
// try to refetch secret in case it has changed
|
128
|
-
await withSecret(secretId, (fetchedSecret: DbSecret) => {
|
129
|
-
setDbSecret(fetchedSecret);
|
130
|
-
cachedSecret = fetchedSecret;
|
131
|
-
});
|
132
|
-
return fn(cachedSecret);
|
133
|
-
}
|
134
|
-
}
|
135
|
-
|
136
20
|
export function checkExpectedSecretKeys<Secret extends GenericSecret>(
|
137
21
|
keys: string[],
|
138
22
|
secret: Secret
|
@@ -1,10 +1,7 @@
|
|
1
1
|
import { SecretHolder } from "./secret-holder";
|
2
|
-
import {
|
3
|
-
DatabaseEnvironmentKeys,
|
4
|
-
RdsProxySecretKey,
|
5
|
-
RdsProxySecret,
|
6
|
-
} from "./dbsecret";
|
2
|
+
import { RdsProxySecretKey, RdsProxySecret } from "./dbsecret";
|
7
3
|
import { getEnvVariable } from "../../../utils/utils";
|
4
|
+
import { DatabaseEnvironmentKeys } from "../../../database/database";
|
8
5
|
|
9
6
|
const RDS_PROXY_SECRET_KEYS = Object.values(RdsProxySecretKey);
|
10
7
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { SecretHolder } from "./secret-holder";
|
2
|
-
import {
|
2
|
+
import { RdsSecret, RdsSecretKey } from "./dbsecret";
|
3
3
|
import { getEnvVariable } from "../../../utils/utils";
|
4
|
+
import { DatabaseEnvironmentKeys } from "../../../database/database";
|
4
5
|
|
5
6
|
const RDS_SECRET_KEYS = Object.values(RdsSecretKey);
|
6
7
|
|
@@ -1,12 +1,8 @@
|
|
1
1
|
import { GenericSecret, getSecret } from "./secret";
|
2
|
-
import {
|
3
|
-
checkExpectedSecretKeys,
|
4
|
-
DatabaseEnvironmentKeys,
|
5
|
-
DbSecret,
|
6
|
-
} from "./dbsecret";
|
2
|
+
import { checkExpectedSecretKeys } from "./dbsecret";
|
7
3
|
import { getEnvVariable } from "../../../utils/utils";
|
8
4
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment
|
10
6
|
const NodeTtl = require("node-ttl");
|
11
7
|
|
12
8
|
const DEFAULT_PREFIX = "";
|
@@ -40,6 +36,7 @@ export class SecretHolder<Secret extends GenericSecret> {
|
|
40
36
|
this.prefix = prefix;
|
41
37
|
this.expectedKeys = expectedKeys;
|
42
38
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
43
40
|
this.secretCache = new NodeTtl(configuration);
|
44
41
|
}
|
45
42
|
|
@@ -48,6 +45,7 @@ export class SecretHolder<Secret extends GenericSecret> {
|
|
48
45
|
|
49
46
|
console.info("refreshing secret " + this.secretId);
|
50
47
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
51
49
|
this.secretCache.push(DEFAULT_SECRET_KEY, secretValue);
|
52
50
|
}
|
53
51
|
|
@@ -90,24 +88,14 @@ export class SecretHolder<Secret extends GenericSecret> {
|
|
90
88
|
}
|
91
89
|
|
92
90
|
private async getSecret<S>(): Promise<S> {
|
93
|
-
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
92
|
+
const secret: S | undefined = this.secretCache.get(DEFAULT_SECRET_KEY);
|
94
93
|
|
95
94
|
if (!secret) {
|
96
95
|
await this.initSecret();
|
97
96
|
}
|
98
97
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
/**
|
103
|
-
* @deprecated Use ProxyHolder
|
104
|
-
*/
|
105
|
-
public async setDatabaseCredentials() {
|
106
|
-
const secret = await this.getSecret<DbSecret>();
|
107
|
-
|
108
|
-
process.env[DatabaseEnvironmentKeys.DB_USER] = secret.username;
|
109
|
-
process.env[DatabaseEnvironmentKeys.DB_PASS] = secret.password;
|
110
|
-
process.env[DatabaseEnvironmentKeys.DB_URI] = secret.host;
|
111
|
-
process.env[DatabaseEnvironmentKeys.DB_RO_URI] = secret.ro_host;
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
99
|
+
return secret ?? (this.secretCache.get(DEFAULT_SECRET_KEY) as S);
|
112
100
|
}
|
113
101
|
}
|