@digitraffic/common 2024.1.24-3 → 2024.3.11-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/dist/__test__/api/handler-factory.test.d.mts +1 -0
- package/dist/__test__/api/handler-factory.test.mjs +43 -0
- package/dist/__test__/api/response.test.d.mts +1 -0
- package/dist/__test__/api/response.test.mjs +86 -0
- package/dist/__test__/imports.test.d.mts +1 -0
- package/dist/__test__/imports.test.mjs +332 -0
- package/dist/__test__/marine/id_utils.test.d.mts +1 -0
- package/dist/__test__/marine/id_utils.test.mjs +44 -0
- package/dist/__test__/promise/promise.test.d.mts +1 -0
- package/dist/__test__/promise/promise.test.mjs +130 -0
- package/dist/__test__/runtime/dt-logger.test.d.mts +1 -0
- package/dist/__test__/runtime/dt-logger.test.mjs +108 -0
- package/dist/__test__/secrets/secret-holder.test.d.mts +1 -0
- package/dist/__test__/secrets/secret-holder.test.mjs +86 -0
- package/dist/__test__/secrets/secret.test.d.mts +1 -0
- package/dist/__test__/secrets/secret.test.mjs +38 -0
- package/dist/__test__/test/httpserver.test.d.mts +1 -0
- package/dist/__test__/test/httpserver.test.mjs +154 -0
- package/dist/__test__/test/mock-ky.test.d.mts +1 -0
- package/dist/__test__/test/mock-ky.test.mjs +46 -0
- package/dist/__test__/types/lambda-response.test.d.mts +1 -0
- package/dist/__test__/types/lambda-response.test.mjs +58 -0
- package/dist/__test__/utils/date-utils.test.d.mts +1 -0
- package/dist/__test__/utils/date-utils.test.mjs +27 -0
- package/dist/__test__/utils/geometry.test.d.mts +1 -0
- package/dist/__test__/utils/geometry.test.mjs +24 -0
- package/dist/__test__/utils/logging.test.d.mts +1 -0
- package/dist/__test__/utils/logging.test.mjs +78 -0
- package/dist/__test__/utils/utils.test.d.mts +1 -0
- package/dist/__test__/utils/utils.test.mjs +43 -0
- package/dist/aws/infra/api/handler-factory.mjs +4 -0
- package/dist/aws/infra/api/integration.d.mts +2 -2
- package/dist/aws/infra/api/integration.mjs +4 -1
- package/dist/aws/infra/api/response.d.mts +1 -1
- package/dist/aws/infra/api/responses.d.mts +1 -1
- package/dist/aws/infra/api/responses.mjs +2 -0
- package/dist/aws/infra/api/static-integration.mjs +1 -1
- package/dist/aws/infra/canaries/canary-alarm.d.mts +1 -1
- package/dist/aws/infra/canaries/canary-alarm.mjs +2 -0
- package/dist/aws/infra/canaries/canary-parameters.mjs +1 -1
- package/dist/aws/infra/canaries/canary-role.mjs +1 -0
- package/dist/aws/infra/canaries/canary.d.mts +2 -2
- package/dist/aws/infra/canaries/canary.mjs +2 -0
- package/dist/aws/infra/canaries/database-canary.d.mts +2 -2
- package/dist/aws/infra/canaries/database-canary.mjs +2 -0
- package/dist/aws/infra/canaries/database-checker.d.mts +1 -1
- package/dist/aws/infra/canaries/database-checker.mjs +7 -1
- package/dist/aws/infra/canaries/url-canary.d.mts +2 -2
- package/dist/aws/infra/canaries/url-canary.mjs +3 -0
- package/dist/aws/infra/canaries/url-checker.d.mts +1 -1
- package/dist/aws/infra/canaries/url-checker.mjs +4 -1
- package/dist/aws/infra/documentation.mjs +5 -1
- package/dist/aws/infra/import-util.d.mts +1 -1
- package/dist/aws/infra/import-util.mjs +4 -3
- package/dist/aws/infra/scheduler.mjs +2 -0
- package/dist/aws/infra/security-rule.d.mts +1 -1
- package/dist/aws/infra/security-rule.mjs +1 -0
- package/dist/aws/infra/sqs-integration.d.mts +1 -1
- package/dist/aws/infra/sqs-integration.mjs +3 -1
- package/dist/aws/infra/sqs-queue.d.mts +1 -1
- package/dist/aws/infra/sqs-queue.mjs +2 -1
- package/dist/aws/infra/stack/lambda-configs.d.mts +4 -4
- package/dist/aws/infra/stack/lambda-configs.mjs +4 -2
- package/dist/aws/infra/stack/monitoredfunction.d.mts +3 -3
- package/dist/aws/infra/stack/monitoredfunction.mjs +23 -18
- package/dist/aws/infra/stack/parameters.mjs +1 -0
- package/dist/aws/infra/stack/rest_apis.d.mts +2 -2
- package/dist/aws/infra/stack/rest_apis.mjs +6 -1
- package/dist/aws/infra/stack/stack-checking-aspect.d.mts +2 -2
- package/dist/aws/infra/stack/stack-checking-aspect.mjs +6 -1
- package/dist/aws/infra/stack/stack.d.mts +5 -5
- package/dist/aws/infra/stack/stack.mjs +9 -0
- package/dist/aws/infra/stack/subscription.mjs +4 -0
- package/dist/aws/infra/stacks/db-dns-stack.d.mts +1 -1
- package/dist/aws/infra/stacks/db-dns-stack.mjs +1 -0
- package/dist/aws/infra/stacks/db-proxy-stack.d.mts +3 -3
- package/dist/aws/infra/stacks/db-proxy-stack.mjs +4 -2
- package/dist/aws/infra/stacks/db-stack.d.mts +3 -3
- package/dist/aws/infra/stacks/db-stack.mjs +11 -7
- package/dist/aws/infra/stacks/intra-stack-configuration.d.mts +1 -1
- package/dist/aws/infra/stacks/network-stack.d.mts +2 -2
- package/dist/aws/infra/stacks/network-stack.mjs +8 -0
- package/dist/aws/infra/usage-plans.d.mts +1 -1
- package/dist/aws/infra/usage-plans.mjs +1 -0
- package/dist/aws/runtime/apikey.d.mts +2 -2
- package/dist/aws/runtime/apikey.mjs +2 -2
- package/dist/aws/runtime/digitraffic-integration-response.d.mts +1 -1
- package/dist/aws/runtime/dt-logger.mjs +6 -2
- package/dist/aws/runtime/messaging.d.mts +2 -2
- package/dist/aws/runtime/messaging.mjs +5 -4
- package/dist/aws/runtime/s3.d.mts +4 -2
- package/dist/aws/runtime/s3.mjs +15 -10
- package/dist/aws/runtime/secrets/dbsecret.d.mts +1 -1
- package/dist/aws/runtime/secrets/proxy-holder.mjs +1 -0
- package/dist/aws/runtime/secrets/rds-holder.mjs +1 -0
- package/dist/aws/runtime/secrets/secret-holder.d.mts +1 -1
- package/dist/aws/runtime/secrets/secret-holder.mjs +6 -1
- package/dist/aws/runtime/secrets/secret.mjs +5 -6
- package/dist/aws/types/errors.mjs +1 -0
- package/dist/aws/types/lambda-response.mjs +5 -0
- package/dist/aws/types/model-with-reference.mjs +1 -1
- package/dist/database/cached.d.mts +1 -1
- package/dist/database/database.mjs +1 -0
- package/dist/database/last-updated.d.mts +1 -1
- package/dist/test/db-testutils.d.mts +1 -1
- package/dist/test/db-testutils.mjs +1 -1
- package/dist/test/httpserver.mjs +7 -3
- package/dist/test/mock-ky.d.mts +2 -0
- package/dist/test/mock-ky.mjs +15 -0
- package/dist/test/secrets-manager.d.mts +3 -2
- package/dist/test/secrets-manager.mjs +14 -16
- package/dist/test/testutils.mjs +1 -1
- package/dist/types/http-error.mjs +1 -0
- package/dist/types/nullable.d.mts +1 -1
- package/dist/utils/api-model.d.mts +2 -2
- package/dist/utils/api-model.mjs +1 -1
- package/dist/utils/geojson-types.d.mts +1 -1
- package/dist/utils/geojson-types.mjs +4 -2
- package/dist/utils/geometry.d.mts +1 -1
- package/dist/utils/geometry.mjs +3 -0
- package/dist/utils/logging.mjs +2 -2
- package/dist/utils/retry.d.mts +2 -2
- package/dist/utils/retry.mjs +2 -2
- package/dist/utils/slack.mjs +4 -3
- package/dist/utils/utils.d.mts +2 -2
- package/package.json +25 -15
- package/src/@types/geojson-validation/index.d.mts +0 -4
- package/src/aws/infra/api/handler-factory.mts +0 -86
- package/src/aws/infra/api/integration.mts +0 -147
- package/src/aws/infra/api/response.mts +0 -165
- package/src/aws/infra/api/responses.mts +0 -127
- package/src/aws/infra/api/static-integration.mts +0 -108
- package/src/aws/infra/canaries/Synthetics.d.mts +0 -21
- package/src/aws/infra/canaries/canary-alarm.mts +0 -33
- package/src/aws/infra/canaries/canary-keys.mts +0 -3
- package/src/aws/infra/canaries/canary-parameters.mts +0 -19
- package/src/aws/infra/canaries/canary-role.mts +0 -73
- package/src/aws/infra/canaries/canary.mts +0 -44
- package/src/aws/infra/canaries/database-canary.mts +0 -98
- package/src/aws/infra/canaries/database-checker.mts +0 -163
- package/src/aws/infra/canaries/url-canary.mts +0 -98
- package/src/aws/infra/canaries/url-checker.mts +0 -388
- package/src/aws/infra/documentation.mts +0 -142
- package/src/aws/infra/import-util.mts +0 -57
- package/src/aws/infra/scheduler.mts +0 -59
- package/src/aws/infra/security-rule.mts +0 -38
- package/src/aws/infra/sqs-integration.mts +0 -106
- package/src/aws/infra/sqs-queue.mts +0 -162
- package/src/aws/infra/stack/lambda-configs.mts +0 -135
- package/src/aws/infra/stack/monitoredfunction.mts +0 -352
- package/src/aws/infra/stack/parameters.mts +0 -74
- package/src/aws/infra/stack/rest_apis.mts +0 -322
- package/src/aws/infra/stack/stack-checking-aspect.mts +0 -233
- package/src/aws/infra/stack/stack.mts +0 -144
- package/src/aws/infra/stack/subscription.mts +0 -58
- package/src/aws/infra/stacks/db-dns-stack.mts +0 -77
- package/src/aws/infra/stacks/db-proxy-stack.mts +0 -134
- package/src/aws/infra/stacks/db-stack.mts +0 -292
- package/src/aws/infra/stacks/intra-stack-configuration.mts +0 -6
- package/src/aws/infra/stacks/network-stack.mts +0 -76
- package/src/aws/infra/usage-plans.mts +0 -50
- package/src/aws/runtime/apikey.mts +0 -9
- package/src/aws/runtime/digitraffic-integration-response.mts +0 -35
- package/src/aws/runtime/dt-logger-default.mts +0 -11
- package/src/aws/runtime/dt-logger.mts +0 -184
- package/src/aws/runtime/environment.mts +0 -22
- package/src/aws/runtime/messaging.mts +0 -26
- package/src/aws/runtime/s3.mts +0 -44
- package/src/aws/runtime/secrets/dbsecret.mts +0 -31
- package/src/aws/runtime/secrets/node-ttl.d.mts +0 -12
- package/src/aws/runtime/secrets/proxy-holder.mts +0 -34
- package/src/aws/runtime/secrets/rds-holder.mts +0 -34
- package/src/aws/runtime/secrets/secret-holder.mts +0 -106
- package/src/aws/runtime/secrets/secret.mts +0 -58
- package/src/aws/types/errors.mts +0 -14
- package/src/aws/types/lambda-response.mts +0 -100
- package/src/aws/types/mediatypes.mts +0 -12
- package/src/aws/types/model-with-reference.mts +0 -8
- package/src/aws/types/proxytypes.mts +0 -27
- package/src/aws/types/tags.mts +0 -3
- package/src/database/cached.mts +0 -64
- package/src/database/database.mts +0 -107
- package/src/database/last-updated.mts +0 -103
- package/src/database/models.mts +0 -7
- package/src/index.mts +0 -2
- package/src/marine/id_utils.mts +0 -30
- package/src/marine/rtz.mts +0 -57
- package/src/test/asserter.mts +0 -58
- package/src/test/db-testutils.mts +0 -52
- package/src/test/httpserver.mts +0 -111
- package/src/test/secrets-manager.mts +0 -37
- package/src/test/testutils.mts +0 -39
- package/src/types/async-timeout-error.mts +0 -5
- package/src/types/aws-env.mts +0 -3
- package/src/types/either.mts +0 -9
- package/src/types/http-error.mts +0 -8
- package/src/types/input-error.mts +0 -2
- package/src/types/language.mts +0 -3
- package/src/types/nullable.mts +0 -21
- package/src/types/traffictype.mts +0 -8
- package/src/types/urn.mts +0 -1
- package/src/types/util-types.mts +0 -10
- package/src/types/validator.mts +0 -10
- package/src/utils/api-model.mts +0 -133
- package/src/utils/base64.mts +0 -16
- package/src/utils/date-utils.mts +0 -53
- package/src/utils/geojson-types.mts +0 -22
- package/src/utils/geometry.mts +0 -171
- package/src/utils/logging.mts +0 -75
- package/src/utils/retry.mts +0 -200
- package/src/utils/slack.mts +0 -26
- package/src/utils/utils.mts +0 -184
@@ -1,322 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
CfnDocumentationPart,
|
3
|
-
EndpointType,
|
4
|
-
GatewayResponse,
|
5
|
-
IResource,
|
6
|
-
JsonSchema,
|
7
|
-
MethodLoggingLevel,
|
8
|
-
MockIntegration,
|
9
|
-
Model,
|
10
|
-
Resource,
|
11
|
-
ResponseType,
|
12
|
-
RestApi,
|
13
|
-
RestApiProps,
|
14
|
-
} from "aws-cdk-lib/aws-apigateway";
|
15
|
-
import {
|
16
|
-
AnyPrincipal,
|
17
|
-
Effect,
|
18
|
-
PolicyDocument,
|
19
|
-
PolicyStatement,
|
20
|
-
} from "aws-cdk-lib/aws-iam";
|
21
|
-
import { Construct } from "constructs";
|
22
|
-
import { getModelReference } from "../../../utils/api-model.mjs";
|
23
|
-
import { MediaType } from "../../types/mediatypes.mjs";
|
24
|
-
import { ModelWithReference } from "../../types/model-with-reference.mjs";
|
25
|
-
import { DocumentationPart, DocumentationProperties } from "../documentation.mjs";
|
26
|
-
import { createDefaultUsagePlan, createUsagePlan } from "../usage-plans.mjs";
|
27
|
-
import { DigitrafficStack } from "./stack.mjs";
|
28
|
-
|
29
|
-
import _ from "lodash";
|
30
|
-
|
31
|
-
export class DigitrafficRestApi extends RestApi {
|
32
|
-
readonly apiKeyIds: string[];
|
33
|
-
readonly enableDocumentation: boolean;
|
34
|
-
|
35
|
-
constructor(
|
36
|
-
stack: DigitrafficStack,
|
37
|
-
apiId: string,
|
38
|
-
apiName: string,
|
39
|
-
allowFromIpAddresses?: string[] | undefined,
|
40
|
-
config?: Partial<RestApiProps>
|
41
|
-
) {
|
42
|
-
const policyDocument =
|
43
|
-
allowFromIpAddresses == null
|
44
|
-
? createDefaultPolicyDocument()
|
45
|
-
: createIpRestrictionPolicyDocument(allowFromIpAddresses);
|
46
|
-
|
47
|
-
// override default config with given extra config
|
48
|
-
const apiConfig = {
|
49
|
-
...{
|
50
|
-
deployOptions: {
|
51
|
-
loggingLevel: MethodLoggingLevel.ERROR,
|
52
|
-
},
|
53
|
-
restApiName: apiName,
|
54
|
-
endpointTypes: [EndpointType.REGIONAL],
|
55
|
-
policy: policyDocument,
|
56
|
-
},
|
57
|
-
...config,
|
58
|
-
};
|
59
|
-
|
60
|
-
super(stack, apiId, apiConfig);
|
61
|
-
|
62
|
-
this.apiKeyIds = [];
|
63
|
-
this.enableDocumentation =
|
64
|
-
stack.configuration.stackFeatures?.enableDocumentation ?? true;
|
65
|
-
|
66
|
-
add404Support(this, stack);
|
67
|
-
}
|
68
|
-
|
69
|
-
hostname(): string {
|
70
|
-
return `${this.restApiId}.execute-api.${
|
71
|
-
(this.stack as DigitrafficStack).region
|
72
|
-
}.amazonaws.com`;
|
73
|
-
}
|
74
|
-
|
75
|
-
createUsagePlan(apiKeyId: string, apiKeyName: string): string {
|
76
|
-
const newKeyId = createUsagePlan(this, apiKeyId, apiKeyName).keyId;
|
77
|
-
|
78
|
-
this.apiKeyIds.push(newKeyId);
|
79
|
-
|
80
|
-
return newKeyId;
|
81
|
-
}
|
82
|
-
|
83
|
-
createUsagePlanV2(apiName: string, apiKey?: string): string {
|
84
|
-
const newKeyId = createDefaultUsagePlan(this, apiName, apiKey).keyId;
|
85
|
-
|
86
|
-
this.apiKeyIds.push(newKeyId);
|
87
|
-
|
88
|
-
return newKeyId;
|
89
|
-
}
|
90
|
-
|
91
|
-
addJsonModel(modelName: string, schema: JsonSchema) {
|
92
|
-
return this.getModelWithReference(
|
93
|
-
this.addModel(modelName, {
|
94
|
-
contentType: MediaType.APPLICATION_JSON,
|
95
|
-
modelName,
|
96
|
-
schema,
|
97
|
-
})
|
98
|
-
);
|
99
|
-
}
|
100
|
-
|
101
|
-
addCSVModel(modelName: string) {
|
102
|
-
return this.getModelWithReference(
|
103
|
-
this.addModel(modelName, {
|
104
|
-
contentType: MediaType.TEXT_CSV,
|
105
|
-
modelName,
|
106
|
-
schema: {},
|
107
|
-
})
|
108
|
-
);
|
109
|
-
}
|
110
|
-
|
111
|
-
private getModelWithReference(model: Model): ModelWithReference {
|
112
|
-
return _.set(
|
113
|
-
model,
|
114
|
-
"modelReference",
|
115
|
-
getModelReference(model.modelId, this.restApiId)
|
116
|
-
) as ModelWithReference;
|
117
|
-
}
|
118
|
-
|
119
|
-
private addDocumentationPart(
|
120
|
-
resource: Resource,
|
121
|
-
parameterName: string,
|
122
|
-
resourceName: string,
|
123
|
-
type: string,
|
124
|
-
properties: DocumentationProperties
|
125
|
-
) {
|
126
|
-
const location: CfnDocumentationPart.LocationProperty = {
|
127
|
-
type,
|
128
|
-
path: resource.path,
|
129
|
-
name: type !== "METHOD" ? parameterName : undefined,
|
130
|
-
};
|
131
|
-
|
132
|
-
new CfnDocumentationPart(this.stack, resourceName, {
|
133
|
-
restApiId: resource.api.restApiId,
|
134
|
-
location,
|
135
|
-
properties: JSON.stringify(properties),
|
136
|
-
});
|
137
|
-
}
|
138
|
-
|
139
|
-
documentResource(
|
140
|
-
resource: Resource,
|
141
|
-
...documentationPart: DocumentationPart[]
|
142
|
-
) {
|
143
|
-
if (this.enableDocumentation) {
|
144
|
-
documentationPart.forEach((dp) =>
|
145
|
-
this.addDocumentationPart(
|
146
|
-
resource,
|
147
|
-
dp.parameterName,
|
148
|
-
`${resource.path}.${dp.parameterName}.Documentation`,
|
149
|
-
dp.type,
|
150
|
-
dp.documentationProperties
|
151
|
-
)
|
152
|
-
);
|
153
|
-
} else {
|
154
|
-
console.info("Skipping documentation for %s", resource.path);
|
155
|
-
}
|
156
|
-
}
|
157
|
-
|
158
|
-
/**
|
159
|
-
* Add support for HTTP OPTIONS to an API GW resource,
|
160
|
-
* this is required for preflight CORS requests made by browsers.
|
161
|
-
* @param apiResource
|
162
|
-
*/
|
163
|
-
addCorsOptions(apiResource: IResource): void {
|
164
|
-
apiResource.addMethod(
|
165
|
-
"OPTIONS",
|
166
|
-
new MockIntegration({
|
167
|
-
integrationResponses: [
|
168
|
-
{
|
169
|
-
statusCode: "200",
|
170
|
-
responseParameters: {
|
171
|
-
"method.response.header.Access-Control-Allow-Headers":
|
172
|
-
"'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Digitraffic-User'",
|
173
|
-
"method.response.header.Access-Control-Allow-Origin":
|
174
|
-
"'*'",
|
175
|
-
"method.response.header.Access-Control-Allow-Methods":
|
176
|
-
"'OPTIONS,GET,HEAD'",
|
177
|
-
},
|
178
|
-
},
|
179
|
-
],
|
180
|
-
requestTemplates: {
|
181
|
-
"application/json": '{"statusCode": 200}',
|
182
|
-
},
|
183
|
-
}),
|
184
|
-
{
|
185
|
-
methodResponses: [
|
186
|
-
{
|
187
|
-
statusCode: "200",
|
188
|
-
responseParameters: {
|
189
|
-
"method.response.header.Access-Control-Allow-Headers":
|
190
|
-
true,
|
191
|
-
"method.response.header.Access-Control-Allow-Methods":
|
192
|
-
true,
|
193
|
-
"method.response.header.Access-Control-Allow-Origin":
|
194
|
-
true,
|
195
|
-
},
|
196
|
-
},
|
197
|
-
],
|
198
|
-
}
|
199
|
-
);
|
200
|
-
}
|
201
|
-
}
|
202
|
-
|
203
|
-
/**
|
204
|
-
* Due to AWS API design API Gateway will always return 403 'Missing Authentication Token' for requests
|
205
|
-
* with a non-existent endpoint. This function translates this response to a 404.
|
206
|
-
* Requests with an invalid or missing API key are not affected (still return 403 'Forbidden').
|
207
|
-
* @param restApi RestApi
|
208
|
-
* @param stack Construct
|
209
|
-
*/
|
210
|
-
export function add404Support(restApi: RestApi, stack: Construct) {
|
211
|
-
new GatewayResponse(
|
212
|
-
stack,
|
213
|
-
`MissingAuthenticationTokenResponse-${restApi.restApiName}`,
|
214
|
-
{
|
215
|
-
restApi,
|
216
|
-
type: ResponseType.MISSING_AUTHENTICATION_TOKEN,
|
217
|
-
statusCode: "404",
|
218
|
-
templates: {
|
219
|
-
[MediaType.APPLICATION_JSON]: '{"message": "Not found"}',
|
220
|
-
},
|
221
|
-
}
|
222
|
-
);
|
223
|
-
}
|
224
|
-
|
225
|
-
export function add401Support(restApi: RestApi, stack: Construct) {
|
226
|
-
new GatewayResponse(
|
227
|
-
stack,
|
228
|
-
`AuthenticationFailedResponse-${restApi.restApiName}`,
|
229
|
-
{
|
230
|
-
restApi,
|
231
|
-
type: ResponseType.UNAUTHORIZED,
|
232
|
-
statusCode: "401",
|
233
|
-
responseHeaders: {
|
234
|
-
"WWW-Authenticate": "'Basic'",
|
235
|
-
},
|
236
|
-
}
|
237
|
-
);
|
238
|
-
}
|
239
|
-
|
240
|
-
/**
|
241
|
-
* Due to AWS API design API Gateway will always return 403 'Missing Authentication Token' for requests
|
242
|
-
* with a non-existent endpoint. This function converts this response to a custom one.
|
243
|
-
* Requests with an invalid or missing API key are not affected (still return 403 'Forbidden').
|
244
|
-
* @param returnCode
|
245
|
-
* @param message
|
246
|
-
* @param restApi RestApi
|
247
|
-
* @param stack Construct
|
248
|
-
*/
|
249
|
-
export function setReturnCodeForMissingAuthenticationToken(
|
250
|
-
returnCode: number,
|
251
|
-
message: string,
|
252
|
-
restApi: RestApi,
|
253
|
-
stack: Construct
|
254
|
-
) {
|
255
|
-
new GatewayResponse(
|
256
|
-
stack,
|
257
|
-
`MissingAuthenticationTokenResponse-${restApi.restApiName}`,
|
258
|
-
{
|
259
|
-
restApi,
|
260
|
-
type: ResponseType.MISSING_AUTHENTICATION_TOKEN,
|
261
|
-
statusCode: `${returnCode}`,
|
262
|
-
templates: {
|
263
|
-
[MediaType.APPLICATION_JSON]: `{"message": ${message}}`,
|
264
|
-
},
|
265
|
-
}
|
266
|
-
);
|
267
|
-
}
|
268
|
-
|
269
|
-
export function createRestApi(
|
270
|
-
stack: Construct,
|
271
|
-
apiId: string,
|
272
|
-
apiName: string,
|
273
|
-
allowFromIpAddresses?: string[] | undefined
|
274
|
-
): RestApi {
|
275
|
-
const policyDocument =
|
276
|
-
allowFromIpAddresses == null
|
277
|
-
? createDefaultPolicyDocument()
|
278
|
-
: createIpRestrictionPolicyDocument(allowFromIpAddresses);
|
279
|
-
const restApi = new RestApi(stack, apiId, {
|
280
|
-
deployOptions: {
|
281
|
-
loggingLevel: MethodLoggingLevel.ERROR,
|
282
|
-
},
|
283
|
-
restApiName: apiName,
|
284
|
-
endpointTypes: [EndpointType.REGIONAL],
|
285
|
-
policy: policyDocument,
|
286
|
-
});
|
287
|
-
add404Support(restApi, stack);
|
288
|
-
return restApi;
|
289
|
-
}
|
290
|
-
|
291
|
-
export function createDefaultPolicyDocument() {
|
292
|
-
return new PolicyDocument({
|
293
|
-
statements: [
|
294
|
-
new PolicyStatement({
|
295
|
-
effect: Effect.ALLOW,
|
296
|
-
actions: ["execute-api:Invoke"],
|
297
|
-
resources: ["*"],
|
298
|
-
principals: [new AnyPrincipal()],
|
299
|
-
}),
|
300
|
-
],
|
301
|
-
});
|
302
|
-
}
|
303
|
-
|
304
|
-
export function createIpRestrictionPolicyDocument(
|
305
|
-
allowFromIpAddresses: string[]
|
306
|
-
): PolicyDocument {
|
307
|
-
return new PolicyDocument({
|
308
|
-
statements: [
|
309
|
-
new PolicyStatement({
|
310
|
-
effect: Effect.ALLOW,
|
311
|
-
conditions: {
|
312
|
-
IpAddress: {
|
313
|
-
"aws:SourceIp": allowFromIpAddresses,
|
314
|
-
},
|
315
|
-
},
|
316
|
-
actions: ["execute-api:Invoke"],
|
317
|
-
resources: ["*"],
|
318
|
-
principals: [new AnyPrincipal()],
|
319
|
-
}),
|
320
|
-
],
|
321
|
-
});
|
322
|
-
}
|
@@ -1,233 +0,0 @@
|
|
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.mjs";
|
5
|
-
import { IConstruct } from "constructs";
|
6
|
-
import { CfnMethod, CfnResource } from "aws-cdk-lib/aws-apigateway";
|
7
|
-
import { CfnQueue } from "aws-cdk-lib/aws-sqs";
|
8
|
-
import { LogRetention } from "aws-cdk-lib/aws-logs";
|
9
|
-
import { kebabCase } from "change-case";
|
10
|
-
import _ from "lodash";
|
11
|
-
import IntegrationProperty = CfnMethod.IntegrationProperty;
|
12
|
-
|
13
|
-
const MAX_CONCURRENCY_LIMIT = 100;
|
14
|
-
const NODE_RUNTIMES = [Runtime.NODEJS_20_X.name, Runtime.NODEJS_18_X.name];
|
15
|
-
|
16
|
-
enum ResourceType {
|
17
|
-
stackName = "STACK_NAME",
|
18
|
-
reservedConcurrentConcurrency = "RESERVED_CONCURRENT_CONCURRENCY",
|
19
|
-
functionTimeout = "FUNCTION_TIMEOUT",
|
20
|
-
functionMemorySize = "FUNCTION_MEMORY_SIZE",
|
21
|
-
functionRuntime = "FUNCTION_RUNTIME",
|
22
|
-
functionName = "FUNCTION_NAME",
|
23
|
-
tagSolution = "TAG_SOLUTION",
|
24
|
-
bucketPublicity = "BUCKET_PUBLICITY",
|
25
|
-
resourcePath = "RESOURCE_PATH",
|
26
|
-
queueEncryption = "QUEUE_ENCRYPTION",
|
27
|
-
logGroupRetention = "LOG_GROUP_RETENTION",
|
28
|
-
}
|
29
|
-
|
30
|
-
export class StackCheckingAspect implements IAspect {
|
31
|
-
private readonly stackShortName?: string;
|
32
|
-
private readonly whitelistedResources?: string[];
|
33
|
-
|
34
|
-
constructor(stackShortName?: string, whitelistedResources?: string[]) {
|
35
|
-
this.stackShortName = stackShortName;
|
36
|
-
this.whitelistedResources = whitelistedResources;
|
37
|
-
}
|
38
|
-
|
39
|
-
static create(stack: DigitrafficStack) {
|
40
|
-
return new StackCheckingAspect(
|
41
|
-
stack.configuration.shortName,
|
42
|
-
stack.configuration.whitelistedResources
|
43
|
-
);
|
44
|
-
}
|
45
|
-
|
46
|
-
public visit(node: IConstruct): void {
|
47
|
-
//console.info("visiting class " + node.constructor.name);
|
48
|
-
|
49
|
-
this.checkStack(node);
|
50
|
-
this.checkFunction(node);
|
51
|
-
this.checkTags(node);
|
52
|
-
this.checkBucket(node);
|
53
|
-
this.checkResourceCasing(node);
|
54
|
-
this.checkQueueEncryption(node);
|
55
|
-
this.checkLogGroupRetention(node);
|
56
|
-
}
|
57
|
-
|
58
|
-
private isWhitelisted(key: string) {
|
59
|
-
return this.whitelistedResources?.some((wl) => {
|
60
|
-
return key.matchAll(new RegExp(wl, "g"));
|
61
|
-
});
|
62
|
-
}
|
63
|
-
|
64
|
-
private addAnnotation(node: IConstruct, key: ResourceType | string, message: string, isError = true) {
|
65
|
-
const resourceKey = `${node.node.path}/${key}`;
|
66
|
-
const isWhiteListed = this.isWhitelisted(resourceKey);
|
67
|
-
const annotationMessage = `${resourceKey}:${message}`;
|
68
|
-
|
69
|
-
// error && whitelisted -> warning
|
70
|
-
// warning && whitelisted -> nothing
|
71
|
-
if (isError && !isWhiteListed) {
|
72
|
-
Annotations.of(node).addError(annotationMessage);
|
73
|
-
} else if ((!isError && !isWhiteListed) || (isError && isWhiteListed)) {
|
74
|
-
Annotations.of(node).addWarning(annotationMessage);
|
75
|
-
}
|
76
|
-
}
|
77
|
-
|
78
|
-
private checkStack(node: IConstruct) {
|
79
|
-
if (node instanceof DigitrafficStack) {
|
80
|
-
if (
|
81
|
-
(node.stackName.includes("Test") || node.stackName.includes("Tst")) &&
|
82
|
-
node.configuration.production
|
83
|
-
) {
|
84
|
-
this.addAnnotation(node, ResourceType.stackName, "Production is set for Test-stack");
|
85
|
-
}
|
86
|
-
|
87
|
-
if (
|
88
|
-
(node.stackName.includes("Prod") || node.stackName.includes("Prd")) &&
|
89
|
-
!node.configuration.production
|
90
|
-
) {
|
91
|
-
this.addAnnotation(
|
92
|
-
node,
|
93
|
-
ResourceType.stackName,
|
94
|
-
"Production is not set for Production-stack"
|
95
|
-
);
|
96
|
-
}
|
97
|
-
}
|
98
|
-
}
|
99
|
-
|
100
|
-
private checkFunction(node: IConstruct) {
|
101
|
-
if (node instanceof CfnFunction) {
|
102
|
-
if (!node.reservedConcurrentExecutions) {
|
103
|
-
this.addAnnotation(
|
104
|
-
node,
|
105
|
-
ResourceType.reservedConcurrentConcurrency,
|
106
|
-
"Function must have reservedConcurrentConcurrency"
|
107
|
-
);
|
108
|
-
} else if (node.reservedConcurrentExecutions > MAX_CONCURRENCY_LIMIT) {
|
109
|
-
this.addAnnotation(
|
110
|
-
node,
|
111
|
-
ResourceType.reservedConcurrentConcurrency,
|
112
|
-
"Function reservedConcurrentConcurrency too high!"
|
113
|
-
);
|
114
|
-
}
|
115
|
-
|
116
|
-
if (!node.timeout) {
|
117
|
-
this.addAnnotation(node, ResourceType.functionTimeout, "Function must have timeout");
|
118
|
-
}
|
119
|
-
|
120
|
-
if (!node.memorySize) {
|
121
|
-
this.addAnnotation(node, ResourceType.functionMemorySize, "Function must have memorySize");
|
122
|
-
}
|
123
|
-
|
124
|
-
if (node.runtime !== undefined && !NODE_RUNTIMES.includes(node.runtime)) {
|
125
|
-
this.addAnnotation(
|
126
|
-
node,
|
127
|
-
ResourceType.functionRuntime,
|
128
|
-
`Function has wrong runtime ${node.runtime}!`
|
129
|
-
);
|
130
|
-
}
|
131
|
-
|
132
|
-
if (
|
133
|
-
this.stackShortName &&
|
134
|
-
node.functionName &&
|
135
|
-
!node.functionName.startsWith(this.stackShortName)
|
136
|
-
) {
|
137
|
-
this.addAnnotation(
|
138
|
-
node,
|
139
|
-
ResourceType.functionName,
|
140
|
-
`Function name does not begin with ${this.stackShortName}`
|
141
|
-
);
|
142
|
-
}
|
143
|
-
}
|
144
|
-
}
|
145
|
-
|
146
|
-
private checkTags(node: IConstruct) {
|
147
|
-
if (node instanceof Stack) {
|
148
|
-
if (!node.tags.tagValues()[SOLUTION_KEY]) {
|
149
|
-
this.addAnnotation(node, ResourceType.tagSolution, "Solution tag is missing");
|
150
|
-
}
|
151
|
-
}
|
152
|
-
}
|
153
|
-
|
154
|
-
private checkBucket(node: IConstruct) {
|
155
|
-
if (node instanceof CfnBucket) {
|
156
|
-
const c = node.publicAccessBlockConfiguration as
|
157
|
-
| CfnBucket.PublicAccessBlockConfigurationProperty
|
158
|
-
| undefined;
|
159
|
-
|
160
|
-
if (
|
161
|
-
c &&
|
162
|
-
(!c.blockPublicAcls ||
|
163
|
-
!c.blockPublicPolicy ||
|
164
|
-
!c.ignorePublicAcls ||
|
165
|
-
!c.restrictPublicBuckets)
|
166
|
-
) {
|
167
|
-
this.addAnnotation(node, ResourceType.bucketPublicity, "Check bucket publicity");
|
168
|
-
}
|
169
|
-
}
|
170
|
-
}
|
171
|
-
|
172
|
-
private static isValidPath(path: string): boolean {
|
173
|
-
// if path includes . or { check only the trailing part of path
|
174
|
-
if (path.includes(".")) {
|
175
|
-
return this.isValidPath(path.split(".")[0]);
|
176
|
-
}
|
177
|
-
|
178
|
-
if (path.includes("{")) {
|
179
|
-
return this.isValidPath(path.split("{")[0]);
|
180
|
-
}
|
181
|
-
|
182
|
-
return kebabCase(path) === path;
|
183
|
-
}
|
184
|
-
|
185
|
-
private static isValidQueryString(name: string) {
|
186
|
-
return _.snakeCase(name) === name;
|
187
|
-
}
|
188
|
-
|
189
|
-
private checkResourceCasing(node: IConstruct) {
|
190
|
-
if (node instanceof CfnResource) {
|
191
|
-
if (!StackCheckingAspect.isValidPath(node.pathPart)) {
|
192
|
-
this.addAnnotation(node, ResourceType.resourcePath, "Path part should be in kebab-case");
|
193
|
-
}
|
194
|
-
} else if (node instanceof CfnMethod) {
|
195
|
-
const integration = node.integration as IntegrationProperty | undefined;
|
196
|
-
|
197
|
-
if (integration?.requestParameters) {
|
198
|
-
Object.keys(integration.requestParameters).forEach((key) => {
|
199
|
-
const split = key.split(".");
|
200
|
-
const type = split[2];
|
201
|
-
const name = split[3];
|
202
|
-
|
203
|
-
if (type === "querystring" && !StackCheckingAspect.isValidQueryString(name)) {
|
204
|
-
this.addAnnotation(node, name, "Querystring should be in snake_case");
|
205
|
-
}
|
206
|
-
});
|
207
|
-
}
|
208
|
-
}
|
209
|
-
}
|
210
|
-
|
211
|
-
private checkQueueEncryption(node: IConstruct) {
|
212
|
-
if (node instanceof CfnQueue) {
|
213
|
-
if (!node.kmsMasterKeyId) {
|
214
|
-
this.addAnnotation(node, ResourceType.queueEncryption, "Queue must have encryption enabled");
|
215
|
-
}
|
216
|
-
}
|
217
|
-
}
|
218
|
-
|
219
|
-
private checkLogGroupRetention(node: IConstruct) {
|
220
|
-
if (node instanceof LogRetention) {
|
221
|
-
const child = node.node.defaultChild as unknown as Record<string, Record<string, string>>;
|
222
|
-
const retention = child._cfnProperties.RetentionInDays;
|
223
|
-
|
224
|
-
if (!retention) {
|
225
|
-
this.addAnnotation(
|
226
|
-
node,
|
227
|
-
ResourceType.logGroupRetention,
|
228
|
-
"Log group must define log group retention"
|
229
|
-
);
|
230
|
-
}
|
231
|
-
}
|
232
|
-
}
|
233
|
-
}
|
@@ -1,144 +0,0 @@
|
|
1
|
-
import { Aspects, Stack, StackProps } from "aws-cdk-lib";
|
2
|
-
import { type ISecurityGroup, IVpc, SecurityGroup, Vpc } from "aws-cdk-lib/aws-ec2";
|
3
|
-
import { ITopic, Topic } from "aws-cdk-lib/aws-sns";
|
4
|
-
import { StringParameter } from "aws-cdk-lib/aws-ssm";
|
5
|
-
import { ISecret, Secret } from "aws-cdk-lib/aws-secretsmanager";
|
6
|
-
import { Function as AWSFunction } from "aws-cdk-lib/aws-lambda";
|
7
|
-
|
8
|
-
import { StackCheckingAspect } from "./stack-checking-aspect.mjs";
|
9
|
-
import { Construct } from "constructs";
|
10
|
-
import { TrafficType } from "../../../types/traffictype.mjs";
|
11
|
-
import { DBLambdaEnvironment } from "./lambda-configs.mjs";
|
12
|
-
|
13
|
-
const SSM_ROOT = "/digitraffic";
|
14
|
-
export const SOLUTION_KEY = "Solution";
|
15
|
-
const MONITORING_ROOT = "/monitoring";
|
16
|
-
|
17
|
-
export const SSM_KEY_WARNING_TOPIC = `${SSM_ROOT}${MONITORING_ROOT}/warning-topic`;
|
18
|
-
export const SSM_KEY_ALARM_TOPIC = `${SSM_ROOT}${MONITORING_ROOT}/alarm-topic`;
|
19
|
-
|
20
|
-
export interface StackConfiguration {
|
21
|
-
readonly shortName: string;
|
22
|
-
readonly secretId?: string;
|
23
|
-
readonly alarmTopicArn: string;
|
24
|
-
readonly warningTopicArn: string;
|
25
|
-
readonly logsDestinationArn?: string;
|
26
|
-
|
27
|
-
readonly vpcId?: string;
|
28
|
-
readonly lambdaDbSgId?: string;
|
29
|
-
readonly privateSubnetIds?: string[];
|
30
|
-
readonly availabilityZones?: string[];
|
31
|
-
|
32
|
-
readonly trafficType: TrafficType;
|
33
|
-
readonly production: boolean;
|
34
|
-
readonly stackProps: StackProps;
|
35
|
-
|
36
|
-
readonly stackFeatures?: {
|
37
|
-
readonly enableCanaries?: boolean;
|
38
|
-
readonly enableDocumentation?: boolean;
|
39
|
-
};
|
40
|
-
|
41
|
-
/// whitelist resources for StackCheckingAspect
|
42
|
-
readonly whitelistedResources?: string[];
|
43
|
-
}
|
44
|
-
|
45
|
-
export class DigitrafficStack extends Stack {
|
46
|
-
readonly vpc?: IVpc;
|
47
|
-
readonly lambdaDbSg?: ISecurityGroup;
|
48
|
-
readonly alarmTopic: ITopic;
|
49
|
-
readonly warningTopic: ITopic;
|
50
|
-
readonly secret?: ISecret;
|
51
|
-
|
52
|
-
readonly configuration: StackConfiguration;
|
53
|
-
|
54
|
-
constructor(
|
55
|
-
scope: Construct,
|
56
|
-
id: string,
|
57
|
-
configuration: StackConfiguration
|
58
|
-
) {
|
59
|
-
super(scope, id, configuration.stackProps);
|
60
|
-
|
61
|
-
this.configuration = configuration;
|
62
|
-
|
63
|
-
if (configuration.secretId) {
|
64
|
-
this.secret = Secret.fromSecretNameV2(
|
65
|
-
this,
|
66
|
-
"Secret",
|
67
|
-
configuration.secretId
|
68
|
-
);
|
69
|
-
}
|
70
|
-
|
71
|
-
// VPC reference construction requires vpcId and availability zones
|
72
|
-
// private subnets are used in Lambda configuration
|
73
|
-
if (configuration.vpcId) {
|
74
|
-
this.vpc = Vpc.fromVpcAttributes(this, "vpc", {
|
75
|
-
vpcId: configuration.vpcId,
|
76
|
-
privateSubnetIds: configuration.privateSubnetIds,
|
77
|
-
availabilityZones: configuration.availabilityZones ?? [],
|
78
|
-
});
|
79
|
-
}
|
80
|
-
|
81
|
-
// security group that allows Lambda database access
|
82
|
-
if (configuration.lambdaDbSgId) {
|
83
|
-
this.lambdaDbSg = SecurityGroup.fromSecurityGroupId(
|
84
|
-
this,
|
85
|
-
"LambdaDbSG",
|
86
|
-
configuration.lambdaDbSgId
|
87
|
-
);
|
88
|
-
}
|
89
|
-
|
90
|
-
this.alarmTopic = Topic.fromTopicArn(
|
91
|
-
this,
|
92
|
-
"AlarmTopic",
|
93
|
-
StringParameter.fromStringParameterName(
|
94
|
-
this,
|
95
|
-
"AlarmTopicParam",
|
96
|
-
SSM_KEY_ALARM_TOPIC
|
97
|
-
).stringValue
|
98
|
-
);
|
99
|
-
this.warningTopic = Topic.fromTopicArn(
|
100
|
-
this,
|
101
|
-
"WarningTopic",
|
102
|
-
StringParameter.fromStringParameterName(
|
103
|
-
this,
|
104
|
-
"WarningTopicParam",
|
105
|
-
SSM_KEY_WARNING_TOPIC
|
106
|
-
).stringValue
|
107
|
-
);
|
108
|
-
|
109
|
-
this.addAspects();
|
110
|
-
}
|
111
|
-
|
112
|
-
addAspects() {
|
113
|
-
Aspects.of(this).add(StackCheckingAspect.create(this));
|
114
|
-
}
|
115
|
-
|
116
|
-
createLambdaEnvironment(): DBLambdaEnvironment {
|
117
|
-
return this.createDefaultLambdaEnvironment(
|
118
|
-
this.configuration.shortName
|
119
|
-
);
|
120
|
-
}
|
121
|
-
|
122
|
-
createDefaultLambdaEnvironment(dbApplication: string): DBLambdaEnvironment {
|
123
|
-
return this.configuration.secretId
|
124
|
-
? {
|
125
|
-
SECRET_ID: this.configuration.secretId,
|
126
|
-
DB_APPLICATION: dbApplication,
|
127
|
-
}
|
128
|
-
: {
|
129
|
-
DB_APPLICATION: dbApplication,
|
130
|
-
};
|
131
|
-
}
|
132
|
-
|
133
|
-
getSecret(): ISecret {
|
134
|
-
if (this.secret === undefined) {
|
135
|
-
throw new Error("Secret is undefined");
|
136
|
-
}
|
137
|
-
return this.secret;
|
138
|
-
}
|
139
|
-
|
140
|
-
grantSecret(...lambdas: AWSFunction[]) {
|
141
|
-
const secret = this.getSecret();
|
142
|
-
lambdas.forEach((l: AWSFunction) => secret.grantRead(l));
|
143
|
-
}
|
144
|
-
}
|