@digitraffic/common 2022.10.25-1 → 2022.10.31-1
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/.editorconfig +9 -0
- package/.eslintignore +4 -0
- package/.eslintrc.json +27 -0
- package/.github/CODEOWNERS +2 -0
- package/.github/workflows/build.yml +36 -0
- package/.github/workflows/eslint.yml +38 -0
- package/.github/workflows/mirror.yml +15 -0
- package/.gitignore +29 -0
- package/.husky/pre-commit +4 -0
- package/.prettierrc.json +10 -0
- package/dist/aws/infra/api/integration.js +52 -0
- package/dist/aws/infra/api/response.js +61 -0
- package/dist/aws/infra/api/responses.js +82 -0
- package/dist/aws/infra/api/static-integration.js +54 -0
- package/dist/aws/infra/canaries/canary-alarm.js +26 -0
- package/dist/aws/infra/canaries/canary-keys.js +7 -0
- package/dist/aws/infra/canaries/canary-parameters.js +3 -0
- package/dist/aws/infra/canaries/canary-role.js +46 -0
- package/dist/aws/infra/canaries/canary.js +32 -0
- package/dist/aws/infra/canaries/database-canary.js +70 -0
- package/dist/aws/infra/canaries/database-checker.js +103 -0
- package/dist/aws/infra/canaries/url-canary.js +47 -0
- package/dist/aws/infra/canaries/url-checker.js +252 -0
- package/dist/aws/infra/documentation.js +95 -0
- package/dist/aws/infra/scheduler.js +31 -0
- package/dist/aws/infra/security-rule.js +39 -0
- package/dist/aws/infra/sqs-integration.js +93 -0
- package/dist/aws/infra/sqs-queue.js +130 -0
- package/dist/aws/infra/stack/lambda-configs.js +105 -0
- package/dist/aws/infra/stack/monitoredfunction.js +143 -0
- package/dist/aws/infra/stack/rest_apis.js +185 -0
- package/dist/aws/infra/stack/stack-checking-aspect.js +174 -0
- package/dist/aws/infra/stack/stack.js +67 -0
- package/dist/aws/infra/stack/subscription.js +42 -0
- package/dist/aws/infra/usage-plans.js +42 -0
- package/dist/aws/runtime/apikey.js +13 -0
- package/dist/aws/runtime/digitraffic-integration-response.js +26 -0
- package/dist/aws/runtime/environment.js +12 -0
- package/dist/aws/runtime/messaging.js +31 -0
- package/dist/aws/runtime/s3.js +30 -0
- package/dist/aws/runtime/secrets/dbsecret.js +96 -0
- package/dist/aws/runtime/secrets/proxy-holder.js +27 -0
- package/dist/aws/runtime/secrets/rds-holder.js +27 -0
- package/dist/aws/runtime/secrets/secret-holder.js +76 -0
- package/dist/aws/runtime/secrets/secret.js +43 -0
- package/dist/aws/types/errors.js +16 -0
- package/dist/aws/types/lambda-response.js +33 -0
- package/dist/aws/types/mediatypes.js +16 -0
- package/dist/aws/types/model-with-reference.js +3 -0
- package/dist/aws/types/proxytypes.js +3 -0
- package/dist/aws/types/tags.js +7 -0
- package/dist/database/cached.js +32 -0
- package/dist/database/database.js +70 -0
- package/dist/database/last-updated.js +54 -0
- package/dist/database/models.js +3 -0
- package/dist/marine/id_utils.js +33 -0
- package/dist/marine/rtz.js +3 -0
- package/dist/test/asserter.js +45 -0
- package/dist/test/db-testutils.js +31 -0
- package/dist/test/httpserver.js +74 -0
- package/dist/test/secret.js +25 -0
- package/dist/test/secrets-manager.js +59 -0
- package/dist/test/testutils.js +44 -0
- package/dist/types/either.js +3 -0
- package/dist/types/input-error.js +7 -0
- package/dist/types/language.js +10 -0
- package/dist/types/traffictype.js +13 -0
- package/dist/types/validator.js +14 -0
- package/dist/utils/api-model.js +129 -0
- package/dist/utils/base64.js +21 -0
- package/dist/utils/date-utils.js +34 -0
- package/dist/utils/geojson-types.js +18 -0
- package/dist/utils/geometry.js +164 -0
- package/dist/utils/retry.js +50 -0
- package/dist/utils/slack.js +25 -0
- package/dist/utils/utils.js +75 -0
- package/jest.config.js +15 -0
- package/package.json +15 -13
- package/src/@types/geojson-validation/index.d.ts +4 -0
- package/src/aws/infra/api/integration.ts +73 -0
- package/src/aws/infra/api/response.ts +67 -0
- package/src/aws/infra/api/responses.ts +124 -0
- package/src/aws/infra/api/static-integration.ts +62 -0
- package/src/aws/infra/canaries/canary-alarm.ts +31 -0
- package/src/aws/infra/canaries/canary-keys.ts +3 -0
- package/{aws/infra/canaries/canary-parameters.d.ts → src/aws/infra/canaries/canary-parameters.ts} +7 -6
- package/src/aws/infra/canaries/canary-role.ts +47 -0
- package/src/aws/infra/canaries/canary.ts +46 -0
- package/src/aws/infra/canaries/database-canary.ts +98 -0
- package/src/aws/infra/canaries/database-checker.ts +155 -0
- package/src/aws/infra/canaries/url-canary.ts +74 -0
- package/src/aws/infra/canaries/url-checker.ts +366 -0
- package/src/aws/infra/documentation.ts +124 -0
- package/src/aws/infra/scheduler.ts +59 -0
- package/src/aws/infra/security-rule.ts +38 -0
- package/src/aws/infra/sqs-integration.ts +102 -0
- package/src/aws/infra/sqs-queue.ts +148 -0
- package/src/aws/infra/stack/lambda-configs.ts +207 -0
- package/src/aws/infra/stack/monitoredfunction.ts +342 -0
- package/src/aws/infra/stack/rest_apis.ts +223 -0
- package/src/aws/infra/stack/stack-checking-aspect.ts +279 -0
- package/src/aws/infra/stack/stack.ts +145 -0
- package/src/aws/infra/stack/subscription.ts +58 -0
- package/src/aws/infra/usage-plans.ts +41 -0
- package/src/aws/runtime/apikey.ts +9 -0
- package/src/aws/runtime/digitraffic-integration-response.ts +28 -0
- package/src/aws/runtime/environment.ts +9 -0
- package/src/aws/runtime/messaging.ts +26 -0
- package/src/aws/runtime/s3.ts +44 -0
- package/src/aws/runtime/secrets/dbsecret.ts +116 -0
- package/src/aws/runtime/secrets/proxy-holder.ts +37 -0
- package/src/aws/runtime/secrets/rds-holder.ts +33 -0
- package/src/aws/runtime/secrets/secret-holder.ts +116 -0
- package/src/aws/runtime/secrets/secret.ts +50 -0
- package/src/aws/types/errors.ts +14 -0
- package/src/aws/types/lambda-response.ts +43 -0
- package/{aws/types/mediatypes.d.ts → src/aws/types/mediatypes.ts} +4 -3
- package/{aws/types/model-with-reference.d.ts → src/aws/types/model-with-reference.ts} +2 -1
- package/src/aws/types/proxytypes.ts +27 -0
- package/src/aws/types/tags.ts +3 -0
- package/src/database/cached.ts +35 -0
- package/src/database/database.ts +96 -0
- package/src/database/last-updated.ts +59 -0
- package/{database/models.d.ts → src/database/models.ts} +1 -0
- package/src/marine/id_utils.ts +30 -0
- package/src/marine/rtz.ts +57 -0
- package/src/test/asserter.ts +48 -0
- package/src/test/db-testutils.ts +44 -0
- package/src/test/httpserver.ts +96 -0
- package/src/test/secret.ts +23 -0
- package/src/test/secrets-manager.ts +34 -0
- package/src/test/testutils.ts +39 -0
- package/src/types/either.ts +3 -0
- package/src/types/input-error.ts +2 -0
- package/src/types/language.ts +3 -0
- package/src/types/traffictype.ts +8 -0
- package/src/types/validator.ts +10 -0
- package/src/utils/api-model.ts +133 -0
- package/src/utils/base64.ts +16 -0
- package/src/utils/date-utils.ts +30 -0
- package/src/utils/geojson-types.ts +22 -0
- package/src/utils/geometry.ts +164 -0
- package/src/utils/retry.ts +49 -0
- package/src/utils/slack.ts +22 -0
- package/src/utils/utils.ts +105 -0
- package/test/marine/id_utils.test.ts +57 -0
- package/test/promise/promise.test.ts +143 -0
- package/test/secrets/dbsecret.test.ts +59 -0
- package/test/secrets/secret-holder.test.ts +143 -0
- package/test/secrets/secret.test.ts +49 -0
- package/test/test/httpserver.test.ts +128 -0
- package/test/utils/date-utils.test.ts +28 -0
- package/test/utils/geometry.test.ts +29 -0
- package/test/utils/utils.test.ts +64 -0
- package/tsconfig.eslint.json +4 -0
- package/tsconfig.json +22 -0
- package/yarn.lock +4060 -0
- package/aws/infra/api/integration.d.ts +0 -21
- package/aws/infra/api/integration.js +0 -52
- package/aws/infra/api/response.d.ts +0 -22
- package/aws/infra/api/response.js +0 -61
- package/aws/infra/api/responses.d.ts +0 -39
- package/aws/infra/api/responses.js +0 -79
- package/aws/infra/api/static-integration.d.ts +0 -15
- package/aws/infra/api/static-integration.js +0 -54
- package/aws/infra/canaries/canary-alarm.d.ts +0 -6
- package/aws/infra/canaries/canary-alarm.js +0 -26
- package/aws/infra/canaries/canary-parameters.js +0 -3
- package/aws/infra/canaries/canary-role.d.ts +0 -6
- package/aws/infra/canaries/canary-role.js +0 -46
- package/aws/infra/canaries/canary.d.ts +0 -8
- package/aws/infra/canaries/canary.js +0 -32
- package/aws/infra/canaries/database-canary.d.ts +0 -18
- package/aws/infra/canaries/database-canary.js +0 -55
- package/aws/infra/canaries/database-checker.d.ts +0 -21
- package/aws/infra/canaries/database-checker.js +0 -109
- package/aws/infra/canaries/url-canary.d.ts +0 -19
- package/aws/infra/canaries/url-canary.js +0 -46
- package/aws/infra/canaries/url-checker.d.ts +0 -46
- package/aws/infra/canaries/url-checker.js +0 -238
- package/aws/infra/documentation.d.ts +0 -56
- package/aws/infra/documentation.js +0 -95
- package/aws/infra/scheduler.d.ts +0 -12
- package/aws/infra/scheduler.js +0 -31
- package/aws/infra/security-rule.d.ts +0 -12
- package/aws/infra/security-rule.js +0 -39
- package/aws/infra/sqs-integration.d.ts +0 -7
- package/aws/infra/sqs-integration.js +0 -93
- package/aws/infra/sqs-queue.d.ts +0 -16
- package/aws/infra/sqs-queue.js +0 -130
- package/aws/infra/stack/lambda-configs.d.ts +0 -72
- package/aws/infra/stack/lambda-configs.js +0 -93
- package/aws/infra/stack/monitoredfunction.d.ts +0 -84
- package/aws/infra/stack/monitoredfunction.js +0 -135
- package/aws/infra/stack/rest_apis.d.ts +0 -41
- package/aws/infra/stack/rest_apis.js +0 -185
- package/aws/infra/stack/stack-checking-aspect.d.ts +0 -21
- package/aws/infra/stack/stack-checking-aspect.js +0 -174
- package/aws/infra/stack/stack.d.ts +0 -44
- package/aws/infra/stack/stack.js +0 -60
- package/aws/infra/stack/subscription.d.ts +0 -17
- package/aws/infra/stack/subscription.js +0 -41
- package/aws/infra/usage-plans.d.ts +0 -15
- package/aws/infra/usage-plans.js +0 -42
- package/aws/runtime/apikey.d.ts +0 -2
- package/aws/runtime/apikey.js +0 -13
- package/aws/runtime/digitraffic-integration-response.d.ts +0 -8
- package/aws/runtime/digitraffic-integration-response.js +0 -26
- package/aws/runtime/environment.d.ts +0 -1
- package/aws/runtime/environment.js +0 -12
- package/aws/runtime/messaging.d.ts +0 -10
- package/aws/runtime/messaging.js +0 -31
- package/aws/runtime/s3.d.ts +0 -2
- package/aws/runtime/s3.js +0 -30
- package/aws/runtime/secrets/dbsecret.d.ts +0 -54
- package/aws/runtime/secrets/dbsecret.js +0 -96
- package/aws/runtime/secrets/proxy-holder.d.ts +0 -9
- package/aws/runtime/secrets/proxy-holder.js +0 -26
- package/aws/runtime/secrets/rds-holder.d.ts +0 -9
- package/aws/runtime/secrets/rds-holder.js +0 -26
- package/aws/runtime/secrets/secret-holder.d.ts +0 -26
- package/aws/runtime/secrets/secret-holder.js +0 -73
- package/aws/runtime/secrets/secret.d.ts +0 -8
- package/aws/runtime/secrets/secret.js +0 -43
- package/aws/types/errors.d.ts +0 -4
- package/aws/types/errors.js +0 -9
- package/aws/types/lambda-response.d.ts +0 -12
- package/aws/types/lambda-response.js +0 -28
- package/aws/types/mediatypes.js +0 -15
- package/aws/types/model-with-reference.js +0 -3
- package/aws/types/proxytypes.d.ts +0 -26
- package/aws/types/proxytypes.js +0 -3
- package/aws/types/tags.d.ts +0 -2
- package/aws/types/tags.js +0 -7
- package/database/cached.d.ts +0 -7
- package/database/cached.js +0 -32
- package/database/database.d.ts +0 -19
- package/database/database.js +0 -62
- package/database/last-updated.d.ts +0 -16
- package/database/last-updated.js +0 -54
- package/database/models.js +0 -3
- package/index.d.ts +0 -1
- package/index.js +0 -18
- package/marine/id_utils.d.ts +0 -3
- package/marine/id_utils.js +0 -33
- package/marine/rtz.d.ts +0 -48
- package/marine/rtz.js +0 -3
- package/test/asserter.d.ts +0 -11
- package/test/asserter.js +0 -45
- package/test/db-testutils.d.ts +0 -2
- package/test/db-testutils.js +0 -31
- package/test/httpserver.d.ts +0 -18
- package/test/httpserver.js +0 -67
- package/test/secret.d.ts +0 -3
- package/test/secret.js +0 -25
- package/test/secrets-manager.d.ts +0 -9
- package/test/secrets-manager.js +0 -59
- package/test/testutils.d.ts +0 -12
- package/test/testutils.js +0 -44
- package/types/input-error.d.ts +0 -2
- package/types/input-error.js +0 -7
- package/types/language.d.ts +0 -5
- package/types/language.js +0 -10
- package/types/traffictype.d.ts +0 -8
- package/types/traffictype.js +0 -13
- package/types/validator.d.ts +0 -4
- package/types/validator.js +0 -14
- package/utils/api-model.d.ts +0 -87
- package/utils/api-model.js +0 -129
- package/utils/base64.d.ts +0 -12
- package/utils/base64.js +0 -21
- package/utils/date-utils.d.ts +0 -17
- package/utils/date-utils.js +0 -34
- package/utils/geojson-types.d.ts +0 -14
- package/utils/geojson-types.js +0 -18
- package/utils/geometry.d.ts +0 -36
- package/utils/geometry.js +0 -140
- package/utils/retry.d.ts +0 -13
- package/utils/retry.js +0 -50
- package/utils/slack.d.ts +0 -5
- package/utils/slack.js +0 -25
- package/utils/utils.d.ts +0 -30
- package/utils/utils.js +0 -64
@@ -0,0 +1,279 @@
|
|
1
|
+
import { Annotations, IAspect, Stack } from "aws-cdk-lib";
|
2
|
+
import { CfnFunction, Runtime } from "aws-cdk-lib/aws-lambda";
|
3
|
+
import { CfnBucket } from "aws-cdk-lib/aws-s3";
|
4
|
+
import { DigitrafficStack, SOLUTION_KEY } from "./stack";
|
5
|
+
import { IConstruct } from "constructs";
|
6
|
+
import { CfnMethod, CfnResource } from "aws-cdk-lib/aws-apigateway";
|
7
|
+
import { paramCase, snakeCase } from "change-case";
|
8
|
+
import { CfnQueue } from "aws-cdk-lib/aws-sqs";
|
9
|
+
import { LogRetention } from "aws-cdk-lib/aws-logs";
|
10
|
+
import IntegrationProperty = CfnMethod.IntegrationProperty;
|
11
|
+
|
12
|
+
const MAX_CONCURRENCY_LIMIT = 100;
|
13
|
+
const NODE_RUNTIME = Runtime.NODEJS_14_X.name;
|
14
|
+
|
15
|
+
enum ResourceType {
|
16
|
+
stackName = "STACK_NAME",
|
17
|
+
reservedConcurrentConcurrency = "RESERVED_CONCURRENT_CONCURRENCY",
|
18
|
+
functionTimeout = "FUNCTION_TIMEOUT",
|
19
|
+
functionMemorySize = "FUNCTION_MEMORY_SIZE",
|
20
|
+
functionRuntime = "FUNCTION_RUNTIME",
|
21
|
+
functionName = "FUNCTION_NAME",
|
22
|
+
tagSolution = "TAG_SOLUTION",
|
23
|
+
bucketPublicity = "BUCKET_PUBLICITY",
|
24
|
+
resourcePath = "RESOURCE_PATH",
|
25
|
+
queueEncryption = "QUEUE_ENCRYPTION",
|
26
|
+
logGroupRetention = "LOG_GROUP_RETENTION",
|
27
|
+
}
|
28
|
+
|
29
|
+
export class StackCheckingAspect implements IAspect {
|
30
|
+
private readonly stackShortName?: string;
|
31
|
+
private readonly whitelistedResources?: string[];
|
32
|
+
|
33
|
+
constructor(stackShortName?: string, whitelistedResources?: string[]) {
|
34
|
+
this.stackShortName = stackShortName;
|
35
|
+
this.whitelistedResources = whitelistedResources;
|
36
|
+
}
|
37
|
+
|
38
|
+
static create(stack: DigitrafficStack) {
|
39
|
+
return new StackCheckingAspect(
|
40
|
+
stack.configuration.shortName,
|
41
|
+
stack.configuration.whitelistedResources
|
42
|
+
);
|
43
|
+
}
|
44
|
+
|
45
|
+
public visit(node: IConstruct): void {
|
46
|
+
//console.info("visiting class " + node.constructor.name);
|
47
|
+
|
48
|
+
this.checkStack(node);
|
49
|
+
this.checkFunction(node);
|
50
|
+
this.checkTags(node);
|
51
|
+
this.checkBucket(node);
|
52
|
+
this.checkResourceCasing(node);
|
53
|
+
this.checkQueueEncryption(node);
|
54
|
+
this.checkLogGroupRetention(node);
|
55
|
+
}
|
56
|
+
|
57
|
+
private isWhitelisted(key: string) {
|
58
|
+
return this.whitelistedResources?.some((wl) => {
|
59
|
+
return key.matchAll(new RegExp(wl, "g"));
|
60
|
+
});
|
61
|
+
}
|
62
|
+
|
63
|
+
private addAnnotation(
|
64
|
+
node: IConstruct,
|
65
|
+
key: ResourceType | string,
|
66
|
+
message: string,
|
67
|
+
isError = true
|
68
|
+
) {
|
69
|
+
const resourceKey = `${node.node.path}/${key}`;
|
70
|
+
const isWhiteListed = this.isWhitelisted(resourceKey);
|
71
|
+
const annotationMessage = `${resourceKey}:${message}`;
|
72
|
+
|
73
|
+
// error && whitelisted -> warning
|
74
|
+
// warning && whitelisted -> nothing
|
75
|
+
if (isError && !isWhiteListed) {
|
76
|
+
Annotations.of(node).addError(annotationMessage);
|
77
|
+
} else if ((!isError && !isWhiteListed) || (isError && isWhiteListed)) {
|
78
|
+
Annotations.of(node).addWarning(annotationMessage);
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
private checkStack(node: IConstruct) {
|
83
|
+
if (node instanceof DigitrafficStack) {
|
84
|
+
if (
|
85
|
+
(node.stackName.includes("Test") ||
|
86
|
+
node.stackName.includes("Tst")) &&
|
87
|
+
node.configuration.production
|
88
|
+
) {
|
89
|
+
this.addAnnotation(
|
90
|
+
node,
|
91
|
+
ResourceType.stackName,
|
92
|
+
"Production is set for Test-stack"
|
93
|
+
);
|
94
|
+
}
|
95
|
+
|
96
|
+
if (
|
97
|
+
(node.stackName.includes("Prod") ||
|
98
|
+
node.stackName.includes("Prd")) &&
|
99
|
+
!node.configuration.production
|
100
|
+
) {
|
101
|
+
this.addAnnotation(
|
102
|
+
node,
|
103
|
+
ResourceType.stackName,
|
104
|
+
"Production is not set for Production-stack"
|
105
|
+
);
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
private checkFunction(node: IConstruct) {
|
111
|
+
if (node instanceof CfnFunction) {
|
112
|
+
if (!node.reservedConcurrentExecutions) {
|
113
|
+
this.addAnnotation(
|
114
|
+
node,
|
115
|
+
ResourceType.reservedConcurrentConcurrency,
|
116
|
+
"Function must have reservedConcurrentConcurrency"
|
117
|
+
);
|
118
|
+
} else if (
|
119
|
+
node.reservedConcurrentExecutions > MAX_CONCURRENCY_LIMIT
|
120
|
+
) {
|
121
|
+
this.addAnnotation(
|
122
|
+
node,
|
123
|
+
ResourceType.reservedConcurrentConcurrency,
|
124
|
+
"Function reservedConcurrentConcurrency too high!"
|
125
|
+
);
|
126
|
+
}
|
127
|
+
|
128
|
+
if (!node.timeout) {
|
129
|
+
this.addAnnotation(
|
130
|
+
node,
|
131
|
+
ResourceType.functionTimeout,
|
132
|
+
"Function must have timeout"
|
133
|
+
);
|
134
|
+
}
|
135
|
+
|
136
|
+
if (!node.memorySize) {
|
137
|
+
this.addAnnotation(
|
138
|
+
node,
|
139
|
+
ResourceType.functionMemorySize,
|
140
|
+
"Function must have memorySize"
|
141
|
+
);
|
142
|
+
}
|
143
|
+
|
144
|
+
if (node.runtime !== NODE_RUNTIME) {
|
145
|
+
this.addAnnotation(
|
146
|
+
node,
|
147
|
+
ResourceType.functionRuntime,
|
148
|
+
`Function has wrong runtime ${node.runtime}!`
|
149
|
+
);
|
150
|
+
}
|
151
|
+
|
152
|
+
if (
|
153
|
+
this.stackShortName &&
|
154
|
+
node.functionName &&
|
155
|
+
!node.functionName.startsWith(this.stackShortName)
|
156
|
+
) {
|
157
|
+
this.addAnnotation(
|
158
|
+
node,
|
159
|
+
ResourceType.functionName,
|
160
|
+
`Function name does not begin with ${this.stackShortName}`
|
161
|
+
);
|
162
|
+
}
|
163
|
+
}
|
164
|
+
}
|
165
|
+
|
166
|
+
private checkTags(node: IConstruct) {
|
167
|
+
if (node instanceof Stack) {
|
168
|
+
if (!node.tags.tagValues()[SOLUTION_KEY]) {
|
169
|
+
this.addAnnotation(
|
170
|
+
node,
|
171
|
+
ResourceType.tagSolution,
|
172
|
+
"Solution tag is missing"
|
173
|
+
);
|
174
|
+
}
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
private checkBucket(node: IConstruct) {
|
179
|
+
if (node instanceof CfnBucket) {
|
180
|
+
const c =
|
181
|
+
node.publicAccessBlockConfiguration as CfnBucket.PublicAccessBlockConfigurationProperty;
|
182
|
+
|
183
|
+
if (c) {
|
184
|
+
if (
|
185
|
+
!c.blockPublicAcls ||
|
186
|
+
!c.blockPublicPolicy ||
|
187
|
+
!c.ignorePublicAcls ||
|
188
|
+
!c.restrictPublicBuckets
|
189
|
+
) {
|
190
|
+
this.addAnnotation(
|
191
|
+
node,
|
192
|
+
ResourceType.bucketPublicity,
|
193
|
+
"Check bucket publicity"
|
194
|
+
);
|
195
|
+
}
|
196
|
+
}
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
private static isValidPath(path: string): boolean {
|
201
|
+
// if path includes . or { check only the trailing part of path
|
202
|
+
if (path.includes(".")) {
|
203
|
+
return this.isValidPath(path.split(".")[0]);
|
204
|
+
}
|
205
|
+
|
206
|
+
if (path.includes("{")) {
|
207
|
+
return this.isValidPath(path.split("{")[0]);
|
208
|
+
}
|
209
|
+
|
210
|
+
return paramCase(path) === path;
|
211
|
+
}
|
212
|
+
|
213
|
+
private static isValidQueryString(name: string) {
|
214
|
+
return snakeCase(name) === name;
|
215
|
+
}
|
216
|
+
|
217
|
+
private checkResourceCasing(node: IConstruct) {
|
218
|
+
if (node instanceof CfnResource) {
|
219
|
+
if (!StackCheckingAspect.isValidPath(node.pathPart)) {
|
220
|
+
this.addAnnotation(
|
221
|
+
node,
|
222
|
+
ResourceType.resourcePath,
|
223
|
+
"Path part should be in kebab-case"
|
224
|
+
);
|
225
|
+
}
|
226
|
+
} else if (node instanceof CfnMethod) {
|
227
|
+
const integration = node.integration as IntegrationProperty;
|
228
|
+
|
229
|
+
if (integration && integration.requestParameters) {
|
230
|
+
Object.keys(integration.requestParameters).forEach((key) => {
|
231
|
+
const split = key.split(".");
|
232
|
+
const type = split[2];
|
233
|
+
const name = split[3];
|
234
|
+
|
235
|
+
if (
|
236
|
+
type === "querystring" &&
|
237
|
+
!StackCheckingAspect.isValidQueryString(name)
|
238
|
+
) {
|
239
|
+
this.addAnnotation(
|
240
|
+
node,
|
241
|
+
name,
|
242
|
+
"Querystring should be in snake_case"
|
243
|
+
);
|
244
|
+
}
|
245
|
+
});
|
246
|
+
}
|
247
|
+
}
|
248
|
+
}
|
249
|
+
|
250
|
+
private checkQueueEncryption(node: IConstruct) {
|
251
|
+
if (node instanceof CfnQueue) {
|
252
|
+
if (!node.kmsMasterKeyId) {
|
253
|
+
this.addAnnotation(
|
254
|
+
node,
|
255
|
+
ResourceType.queueEncryption,
|
256
|
+
"Queue must have encryption enabled"
|
257
|
+
);
|
258
|
+
}
|
259
|
+
}
|
260
|
+
}
|
261
|
+
|
262
|
+
private checkLogGroupRetention(node: IConstruct) {
|
263
|
+
if (node instanceof LogRetention) {
|
264
|
+
const child = node.node.defaultChild as unknown as Record<
|
265
|
+
string,
|
266
|
+
Record<string, string>
|
267
|
+
>;
|
268
|
+
const retention = child._cfnProperties.RetentionInDays;
|
269
|
+
|
270
|
+
if (!retention) {
|
271
|
+
this.addAnnotation(
|
272
|
+
node,
|
273
|
+
ResourceType.logGroupRetention,
|
274
|
+
"Log group must define log group retention"
|
275
|
+
);
|
276
|
+
}
|
277
|
+
}
|
278
|
+
}
|
279
|
+
}
|
@@ -0,0 +1,145 @@
|
|
1
|
+
import { Aspects, Stack, StackProps } from "aws-cdk-lib";
|
2
|
+
import { IVpc, SecurityGroup, Vpc } from "aws-cdk-lib/aws-ec2";
|
3
|
+
import { ISecurityGroup } from "aws-cdk-lib/aws-ec2/lib/security-group";
|
4
|
+
import { ITopic, Topic } from "aws-cdk-lib/aws-sns";
|
5
|
+
import { StringParameter } from "aws-cdk-lib/aws-ssm";
|
6
|
+
import { ISecret, Secret } from "aws-cdk-lib/aws-secretsmanager";
|
7
|
+
import { Function as AWSFunction } from "aws-cdk-lib/aws-lambda";
|
8
|
+
|
9
|
+
import { StackCheckingAspect } from "./stack-checking-aspect";
|
10
|
+
import { Construct } from "constructs";
|
11
|
+
import { TrafficType } from "../../../types/traffictype";
|
12
|
+
import { DBLambdaEnvironment } from "./lambda-configs";
|
13
|
+
|
14
|
+
const SSM_ROOT = "/digitraffic";
|
15
|
+
export const SOLUTION_KEY = "Solution";
|
16
|
+
const MONITORING_ROOT = "/monitoring";
|
17
|
+
|
18
|
+
export const SSM_KEY_WARNING_TOPIC = `${SSM_ROOT}${MONITORING_ROOT}/warning-topic`;
|
19
|
+
export const SSM_KEY_ALARM_TOPIC = `${SSM_ROOT}${MONITORING_ROOT}/alarm-topic`;
|
20
|
+
|
21
|
+
export interface StackConfiguration {
|
22
|
+
readonly shortName: string;
|
23
|
+
readonly secretId?: string;
|
24
|
+
readonly alarmTopicArn: string;
|
25
|
+
readonly warningTopicArn: string;
|
26
|
+
readonly logsDestinationArn?: string;
|
27
|
+
|
28
|
+
readonly vpcId?: string;
|
29
|
+
readonly lambdaDbSgId?: string;
|
30
|
+
readonly privateSubnetIds?: string[];
|
31
|
+
readonly availabilityZones?: string[];
|
32
|
+
|
33
|
+
readonly trafficType: TrafficType;
|
34
|
+
readonly production: boolean;
|
35
|
+
readonly stackProps: StackProps;
|
36
|
+
|
37
|
+
readonly stackFeatures?: {
|
38
|
+
readonly enableCanaries?: boolean;
|
39
|
+
readonly enableDocumentation?: boolean;
|
40
|
+
};
|
41
|
+
|
42
|
+
/// whitelist resources for StackCheckingAspect
|
43
|
+
readonly whitelistedResources?: string[];
|
44
|
+
}
|
45
|
+
|
46
|
+
export class DigitrafficStack extends Stack {
|
47
|
+
readonly vpc?: IVpc;
|
48
|
+
readonly lambdaDbSg?: ISecurityGroup;
|
49
|
+
readonly alarmTopic: ITopic;
|
50
|
+
readonly warningTopic: ITopic;
|
51
|
+
readonly secret?: ISecret;
|
52
|
+
|
53
|
+
readonly configuration: StackConfiguration;
|
54
|
+
|
55
|
+
constructor(
|
56
|
+
scope: Construct,
|
57
|
+
id: string,
|
58
|
+
configuration: StackConfiguration
|
59
|
+
) {
|
60
|
+
super(scope, id, configuration.stackProps);
|
61
|
+
|
62
|
+
this.configuration = configuration;
|
63
|
+
|
64
|
+
if (configuration.secretId) {
|
65
|
+
this.secret = Secret.fromSecretNameV2(
|
66
|
+
this,
|
67
|
+
"Secret",
|
68
|
+
configuration.secretId
|
69
|
+
);
|
70
|
+
}
|
71
|
+
|
72
|
+
// VPC reference construction requires vpcId and availability zones
|
73
|
+
// private subnets are used in Lambda configuration
|
74
|
+
if (configuration.vpcId) {
|
75
|
+
this.vpc = Vpc.fromVpcAttributes(this, "vpc", {
|
76
|
+
vpcId: configuration.vpcId,
|
77
|
+
privateSubnetIds: configuration.privateSubnetIds,
|
78
|
+
availabilityZones: configuration.availabilityZones ?? [],
|
79
|
+
});
|
80
|
+
}
|
81
|
+
|
82
|
+
// security group that allows Lambda database access
|
83
|
+
if (configuration.lambdaDbSgId) {
|
84
|
+
this.lambdaDbSg = SecurityGroup.fromSecurityGroupId(
|
85
|
+
this,
|
86
|
+
"LambdaDbSG",
|
87
|
+
configuration.lambdaDbSgId
|
88
|
+
);
|
89
|
+
}
|
90
|
+
|
91
|
+
this.alarmTopic = Topic.fromTopicArn(
|
92
|
+
this,
|
93
|
+
"AlarmTopic",
|
94
|
+
StringParameter.fromStringParameterName(
|
95
|
+
this,
|
96
|
+
"AlarmTopicParam",
|
97
|
+
SSM_KEY_ALARM_TOPIC
|
98
|
+
).stringValue
|
99
|
+
);
|
100
|
+
this.warningTopic = Topic.fromTopicArn(
|
101
|
+
this,
|
102
|
+
"WarningTopic",
|
103
|
+
StringParameter.fromStringParameterName(
|
104
|
+
this,
|
105
|
+
"WarningTopicParam",
|
106
|
+
SSM_KEY_WARNING_TOPIC
|
107
|
+
).stringValue
|
108
|
+
);
|
109
|
+
|
110
|
+
this.addAspects();
|
111
|
+
}
|
112
|
+
|
113
|
+
addAspects() {
|
114
|
+
Aspects.of(this).add(StackCheckingAspect.create(this));
|
115
|
+
}
|
116
|
+
|
117
|
+
createLambdaEnvironment(): DBLambdaEnvironment {
|
118
|
+
return this.createDefaultLambdaEnvironment(
|
119
|
+
this.configuration.shortName
|
120
|
+
);
|
121
|
+
}
|
122
|
+
|
123
|
+
createDefaultLambdaEnvironment(dbApplication: string): DBLambdaEnvironment {
|
124
|
+
return this.configuration.secretId
|
125
|
+
? {
|
126
|
+
SECRET_ID: this.configuration.secretId,
|
127
|
+
DB_APPLICATION: dbApplication,
|
128
|
+
}
|
129
|
+
: {
|
130
|
+
DB_APPLICATION: dbApplication,
|
131
|
+
};
|
132
|
+
}
|
133
|
+
|
134
|
+
getSecret(): ISecret {
|
135
|
+
if (this.secret === undefined) {
|
136
|
+
throw new Error("Secret is undefined");
|
137
|
+
}
|
138
|
+
return this.secret;
|
139
|
+
}
|
140
|
+
|
141
|
+
grantSecret(...lambdas: AWSFunction[]) {
|
142
|
+
const secret = this.getSecret();
|
143
|
+
lambdas.forEach((l: AWSFunction) => secret.grantRead(l));
|
144
|
+
}
|
145
|
+
}
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import { CfnSubscriptionFilter } from "aws-cdk-lib/aws-logs";
|
2
|
+
import { Function as AWSFunction } from "aws-cdk-lib/aws-lambda";
|
3
|
+
import { DigitrafficStack } from "./stack";
|
4
|
+
import { Construct } from "constructs";
|
5
|
+
import { MonitoredFunction } from "./monitoredfunction";
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Creates a subscription filter that subscribes to a Lambda Log Group and delivers the logs to another destination.
|
9
|
+
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-subscriptionfilter.html
|
10
|
+
* @param lambda The Lambda function, needed to create a dependency
|
11
|
+
* @param lambdaName The Lambda name from which the Log Group name is derived
|
12
|
+
* @param logDestinationArn Destination for streamed logs
|
13
|
+
* @param stack CloudFormation stack
|
14
|
+
*/
|
15
|
+
export function createSubscription(
|
16
|
+
lambda: AWSFunction,
|
17
|
+
lambdaName: string,
|
18
|
+
logDestinationArn: string | undefined,
|
19
|
+
stack: Construct
|
20
|
+
): CfnSubscriptionFilter | undefined {
|
21
|
+
if (logDestinationArn == undefined) {
|
22
|
+
return undefined;
|
23
|
+
}
|
24
|
+
const filter = new CfnSubscriptionFilter(
|
25
|
+
stack,
|
26
|
+
`${lambdaName}LogsSubscription`,
|
27
|
+
{
|
28
|
+
logGroupName: `/aws/lambda/${lambdaName}`,
|
29
|
+
filterPattern: "",
|
30
|
+
destinationArn: logDestinationArn,
|
31
|
+
}
|
32
|
+
);
|
33
|
+
|
34
|
+
filter.node.addDependency(lambda);
|
35
|
+
|
36
|
+
return filter;
|
37
|
+
}
|
38
|
+
|
39
|
+
export class DigitrafficLogSubscriptions {
|
40
|
+
constructor(stack: DigitrafficStack, ...lambdas: MonitoredFunction[]) {
|
41
|
+
const destinationArn = stack.configuration.logsDestinationArn;
|
42
|
+
if (destinationArn !== undefined) {
|
43
|
+
lambdas.forEach((lambda) => {
|
44
|
+
const filter = new CfnSubscriptionFilter(
|
45
|
+
stack,
|
46
|
+
`${lambda.givenName}LogsSubscription`,
|
47
|
+
{
|
48
|
+
logGroupName: `/aws/lambda/${lambda.givenName}`,
|
49
|
+
filterPattern: "",
|
50
|
+
destinationArn,
|
51
|
+
}
|
52
|
+
);
|
53
|
+
|
54
|
+
filter.node.addDependency(lambda);
|
55
|
+
});
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import {IApiKey, RestApi} from 'aws-cdk-lib/aws-apigateway';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Creates an usage plan for a REST API with a single API key
|
5
|
+
* @param api The REST API
|
6
|
+
* @param apiKeyId Id for the API key, this is a surrogate id for CDK, not displayed anywhere
|
7
|
+
* @param apiKeyName Name for the API key, this is displayed in the AWS Console
|
8
|
+
* @deprecated Creates randomized API key names, use createDefaultUsagePlan instead
|
9
|
+
*/
|
10
|
+
export function createUsagePlan(api: RestApi, apiKeyId: string, apiKeyName: string): IApiKey {
|
11
|
+
const apiKey = api.addApiKey(apiKeyId);
|
12
|
+
const plan = api.addUsagePlan(apiKeyName, {
|
13
|
+
name: apiKeyName,
|
14
|
+
});
|
15
|
+
plan.addApiStage({
|
16
|
+
stage: api.deploymentStage,
|
17
|
+
});
|
18
|
+
plan.addApiKey(apiKey);
|
19
|
+
|
20
|
+
return apiKey;
|
21
|
+
}
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Creates a default usage plan for a REST API with a single API key
|
25
|
+
* @param api The REST API
|
26
|
+
* @param apiName Name of the api. Will generate key: apiName + ' API Key' and plan: apiName + ' API Usage Plan'
|
27
|
+
*/
|
28
|
+
export function createDefaultUsagePlan(api: RestApi, apiName: string): IApiKey {
|
29
|
+
const apiKeyName = apiName + ' API Key';
|
30
|
+
const usagePlanName = apiName + ' API Usage Plan';
|
31
|
+
const apiKey = api.addApiKey(apiKeyName, { apiKeyName: apiKeyName });
|
32
|
+
const plan = api.addUsagePlan(usagePlanName, {
|
33
|
+
name: usagePlanName,
|
34
|
+
});
|
35
|
+
plan.addApiStage({
|
36
|
+
stage: api.deploymentStage,
|
37
|
+
});
|
38
|
+
plan.addApiKey(apiKey);
|
39
|
+
|
40
|
+
return apiKey;
|
41
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import {IntegrationResponse} from "aws-cdk-lib/aws-apigateway";
|
2
|
+
import {MediaType} from "../types/mediatypes";
|
3
|
+
import {RESPONSE_DEFAULT_LAMBDA} from "../infra/api/response";
|
4
|
+
|
5
|
+
export abstract class DigitrafficIntegrationResponse {
|
6
|
+
|
7
|
+
static ok(mediaType: MediaType): IntegrationResponse {
|
8
|
+
return this.create("200", mediaType);
|
9
|
+
}
|
10
|
+
|
11
|
+
static badRequest(mediaType?: MediaType): IntegrationResponse {
|
12
|
+
return this.create("400", mediaType ?? MediaType.TEXT_PLAIN);
|
13
|
+
}
|
14
|
+
|
15
|
+
static notImplemented(mediaType?: MediaType): IntegrationResponse {
|
16
|
+
return this.create("501", mediaType ?? MediaType.TEXT_PLAIN);
|
17
|
+
}
|
18
|
+
|
19
|
+
static create(statusCode: string, mediaType: MediaType): IntegrationResponse {
|
20
|
+
return {
|
21
|
+
statusCode,
|
22
|
+
responseTemplates: {
|
23
|
+
[mediaType]: RESPONSE_DEFAULT_LAMBDA,
|
24
|
+
},
|
25
|
+
};
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import {SNS} from "aws-sdk";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Utility function for publishing SNS messages.
|
5
|
+
* Made because using *await* with AWS APIs doesn't require calling promise() but nothing works if it isn't called.
|
6
|
+
* Retries a single time in case of failure.
|
7
|
+
* @param message
|
8
|
+
* @param topicArn
|
9
|
+
* @param sns
|
10
|
+
*/
|
11
|
+
export async function snsPublish(message: string, topicArn: string, sns: SNS) {
|
12
|
+
const publishParams = {
|
13
|
+
Message: message,
|
14
|
+
TopicArn: topicArn,
|
15
|
+
};
|
16
|
+
try {
|
17
|
+
await sns.publish(publishParams).promise();
|
18
|
+
} catch (error) {
|
19
|
+
console.error('method=snsPublish error, retrying', error);
|
20
|
+
try {
|
21
|
+
await sns.publish(publishParams).promise();
|
22
|
+
} catch (e2) {
|
23
|
+
console.error('method=snsPublish error after retry', e2);
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import {S3} from "aws-sdk";
|
2
|
+
|
3
|
+
export async function uploadToS3<Body extends S3.Body | undefined>(
|
4
|
+
bucketName: string,
|
5
|
+
body: Body,
|
6
|
+
objectName: string,
|
7
|
+
cannedAcl?: string,
|
8
|
+
contentType?: string,
|
9
|
+
) {
|
10
|
+
|
11
|
+
const s3 = new S3();
|
12
|
+
try {
|
13
|
+
await doUpload(
|
14
|
+
s3, bucketName, body, objectName, cannedAcl, contentType,
|
15
|
+
);
|
16
|
+
} catch (error) {
|
17
|
+
console.warn('method=uploadToS3 retrying upload to bucket %s', bucketName);
|
18
|
+
try {
|
19
|
+
await doUpload(
|
20
|
+
s3, bucketName, body, objectName, cannedAcl, contentType,
|
21
|
+
);
|
22
|
+
} catch (e2) {
|
23
|
+
console.error('method=uploadToS3 failed retrying upload to bucket %s', bucketName);
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
function doUpload<Body extends S3.Body | undefined>(
|
29
|
+
s3: S3,
|
30
|
+
bucketName: string,
|
31
|
+
body: Body,
|
32
|
+
filename: string,
|
33
|
+
cannedAcl?: string,
|
34
|
+
contentType?: string,
|
35
|
+
) {
|
36
|
+
|
37
|
+
return s3.upload({
|
38
|
+
Bucket: bucketName,
|
39
|
+
Body: body,
|
40
|
+
Key: filename,
|
41
|
+
ACL: cannedAcl,
|
42
|
+
ContentType: contentType,
|
43
|
+
}).promise();
|
44
|
+
}
|