@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.
Files changed (283) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintignore +4 -0
  3. package/.eslintrc.json +27 -0
  4. package/.github/CODEOWNERS +2 -0
  5. package/.github/workflows/build.yml +36 -0
  6. package/.github/workflows/eslint.yml +38 -0
  7. package/.github/workflows/mirror.yml +15 -0
  8. package/.gitignore +29 -0
  9. package/.husky/pre-commit +4 -0
  10. package/.prettierrc.json +10 -0
  11. package/dist/aws/infra/api/integration.js +52 -0
  12. package/dist/aws/infra/api/response.js +61 -0
  13. package/dist/aws/infra/api/responses.js +82 -0
  14. package/dist/aws/infra/api/static-integration.js +54 -0
  15. package/dist/aws/infra/canaries/canary-alarm.js +26 -0
  16. package/dist/aws/infra/canaries/canary-keys.js +7 -0
  17. package/dist/aws/infra/canaries/canary-parameters.js +3 -0
  18. package/dist/aws/infra/canaries/canary-role.js +46 -0
  19. package/dist/aws/infra/canaries/canary.js +32 -0
  20. package/dist/aws/infra/canaries/database-canary.js +70 -0
  21. package/dist/aws/infra/canaries/database-checker.js +103 -0
  22. package/dist/aws/infra/canaries/url-canary.js +47 -0
  23. package/dist/aws/infra/canaries/url-checker.js +252 -0
  24. package/dist/aws/infra/documentation.js +95 -0
  25. package/dist/aws/infra/scheduler.js +31 -0
  26. package/dist/aws/infra/security-rule.js +39 -0
  27. package/dist/aws/infra/sqs-integration.js +93 -0
  28. package/dist/aws/infra/sqs-queue.js +130 -0
  29. package/dist/aws/infra/stack/lambda-configs.js +105 -0
  30. package/dist/aws/infra/stack/monitoredfunction.js +143 -0
  31. package/dist/aws/infra/stack/rest_apis.js +185 -0
  32. package/dist/aws/infra/stack/stack-checking-aspect.js +174 -0
  33. package/dist/aws/infra/stack/stack.js +67 -0
  34. package/dist/aws/infra/stack/subscription.js +42 -0
  35. package/dist/aws/infra/usage-plans.js +42 -0
  36. package/dist/aws/runtime/apikey.js +13 -0
  37. package/dist/aws/runtime/digitraffic-integration-response.js +26 -0
  38. package/dist/aws/runtime/environment.js +12 -0
  39. package/dist/aws/runtime/messaging.js +31 -0
  40. package/dist/aws/runtime/s3.js +30 -0
  41. package/dist/aws/runtime/secrets/dbsecret.js +96 -0
  42. package/dist/aws/runtime/secrets/proxy-holder.js +27 -0
  43. package/dist/aws/runtime/secrets/rds-holder.js +27 -0
  44. package/dist/aws/runtime/secrets/secret-holder.js +76 -0
  45. package/dist/aws/runtime/secrets/secret.js +43 -0
  46. package/dist/aws/types/errors.js +16 -0
  47. package/dist/aws/types/lambda-response.js +33 -0
  48. package/dist/aws/types/mediatypes.js +16 -0
  49. package/dist/aws/types/model-with-reference.js +3 -0
  50. package/dist/aws/types/proxytypes.js +3 -0
  51. package/dist/aws/types/tags.js +7 -0
  52. package/dist/database/cached.js +32 -0
  53. package/dist/database/database.js +70 -0
  54. package/dist/database/last-updated.js +54 -0
  55. package/dist/database/models.js +3 -0
  56. package/dist/marine/id_utils.js +33 -0
  57. package/dist/marine/rtz.js +3 -0
  58. package/dist/test/asserter.js +45 -0
  59. package/dist/test/db-testutils.js +31 -0
  60. package/dist/test/httpserver.js +74 -0
  61. package/dist/test/secret.js +25 -0
  62. package/dist/test/secrets-manager.js +59 -0
  63. package/dist/test/testutils.js +44 -0
  64. package/dist/types/either.js +3 -0
  65. package/dist/types/input-error.js +7 -0
  66. package/dist/types/language.js +10 -0
  67. package/dist/types/traffictype.js +13 -0
  68. package/dist/types/validator.js +14 -0
  69. package/dist/utils/api-model.js +129 -0
  70. package/dist/utils/base64.js +21 -0
  71. package/dist/utils/date-utils.js +34 -0
  72. package/dist/utils/geojson-types.js +18 -0
  73. package/dist/utils/geometry.js +164 -0
  74. package/dist/utils/retry.js +50 -0
  75. package/dist/utils/slack.js +25 -0
  76. package/dist/utils/utils.js +75 -0
  77. package/jest.config.js +15 -0
  78. package/package.json +15 -13
  79. package/src/@types/geojson-validation/index.d.ts +4 -0
  80. package/src/aws/infra/api/integration.ts +73 -0
  81. package/src/aws/infra/api/response.ts +67 -0
  82. package/src/aws/infra/api/responses.ts +124 -0
  83. package/src/aws/infra/api/static-integration.ts +62 -0
  84. package/src/aws/infra/canaries/canary-alarm.ts +31 -0
  85. package/src/aws/infra/canaries/canary-keys.ts +3 -0
  86. package/{aws/infra/canaries/canary-parameters.d.ts → src/aws/infra/canaries/canary-parameters.ts} +7 -6
  87. package/src/aws/infra/canaries/canary-role.ts +47 -0
  88. package/src/aws/infra/canaries/canary.ts +46 -0
  89. package/src/aws/infra/canaries/database-canary.ts +98 -0
  90. package/src/aws/infra/canaries/database-checker.ts +155 -0
  91. package/src/aws/infra/canaries/url-canary.ts +74 -0
  92. package/src/aws/infra/canaries/url-checker.ts +366 -0
  93. package/src/aws/infra/documentation.ts +124 -0
  94. package/src/aws/infra/scheduler.ts +59 -0
  95. package/src/aws/infra/security-rule.ts +38 -0
  96. package/src/aws/infra/sqs-integration.ts +102 -0
  97. package/src/aws/infra/sqs-queue.ts +148 -0
  98. package/src/aws/infra/stack/lambda-configs.ts +207 -0
  99. package/src/aws/infra/stack/monitoredfunction.ts +342 -0
  100. package/src/aws/infra/stack/rest_apis.ts +223 -0
  101. package/src/aws/infra/stack/stack-checking-aspect.ts +279 -0
  102. package/src/aws/infra/stack/stack.ts +145 -0
  103. package/src/aws/infra/stack/subscription.ts +58 -0
  104. package/src/aws/infra/usage-plans.ts +41 -0
  105. package/src/aws/runtime/apikey.ts +9 -0
  106. package/src/aws/runtime/digitraffic-integration-response.ts +28 -0
  107. package/src/aws/runtime/environment.ts +9 -0
  108. package/src/aws/runtime/messaging.ts +26 -0
  109. package/src/aws/runtime/s3.ts +44 -0
  110. package/src/aws/runtime/secrets/dbsecret.ts +116 -0
  111. package/src/aws/runtime/secrets/proxy-holder.ts +37 -0
  112. package/src/aws/runtime/secrets/rds-holder.ts +33 -0
  113. package/src/aws/runtime/secrets/secret-holder.ts +116 -0
  114. package/src/aws/runtime/secrets/secret.ts +50 -0
  115. package/src/aws/types/errors.ts +14 -0
  116. package/src/aws/types/lambda-response.ts +43 -0
  117. package/{aws/types/mediatypes.d.ts → src/aws/types/mediatypes.ts} +4 -3
  118. package/{aws/types/model-with-reference.d.ts → src/aws/types/model-with-reference.ts} +2 -1
  119. package/src/aws/types/proxytypes.ts +27 -0
  120. package/src/aws/types/tags.ts +3 -0
  121. package/src/database/cached.ts +35 -0
  122. package/src/database/database.ts +96 -0
  123. package/src/database/last-updated.ts +59 -0
  124. package/{database/models.d.ts → src/database/models.ts} +1 -0
  125. package/src/marine/id_utils.ts +30 -0
  126. package/src/marine/rtz.ts +57 -0
  127. package/src/test/asserter.ts +48 -0
  128. package/src/test/db-testutils.ts +44 -0
  129. package/src/test/httpserver.ts +96 -0
  130. package/src/test/secret.ts +23 -0
  131. package/src/test/secrets-manager.ts +34 -0
  132. package/src/test/testutils.ts +39 -0
  133. package/src/types/either.ts +3 -0
  134. package/src/types/input-error.ts +2 -0
  135. package/src/types/language.ts +3 -0
  136. package/src/types/traffictype.ts +8 -0
  137. package/src/types/validator.ts +10 -0
  138. package/src/utils/api-model.ts +133 -0
  139. package/src/utils/base64.ts +16 -0
  140. package/src/utils/date-utils.ts +30 -0
  141. package/src/utils/geojson-types.ts +22 -0
  142. package/src/utils/geometry.ts +164 -0
  143. package/src/utils/retry.ts +49 -0
  144. package/src/utils/slack.ts +22 -0
  145. package/src/utils/utils.ts +105 -0
  146. package/test/marine/id_utils.test.ts +57 -0
  147. package/test/promise/promise.test.ts +143 -0
  148. package/test/secrets/dbsecret.test.ts +59 -0
  149. package/test/secrets/secret-holder.test.ts +143 -0
  150. package/test/secrets/secret.test.ts +49 -0
  151. package/test/test/httpserver.test.ts +128 -0
  152. package/test/utils/date-utils.test.ts +28 -0
  153. package/test/utils/geometry.test.ts +29 -0
  154. package/test/utils/utils.test.ts +64 -0
  155. package/tsconfig.eslint.json +4 -0
  156. package/tsconfig.json +22 -0
  157. package/yarn.lock +4060 -0
  158. package/aws/infra/api/integration.d.ts +0 -21
  159. package/aws/infra/api/integration.js +0 -52
  160. package/aws/infra/api/response.d.ts +0 -22
  161. package/aws/infra/api/response.js +0 -61
  162. package/aws/infra/api/responses.d.ts +0 -39
  163. package/aws/infra/api/responses.js +0 -79
  164. package/aws/infra/api/static-integration.d.ts +0 -15
  165. package/aws/infra/api/static-integration.js +0 -54
  166. package/aws/infra/canaries/canary-alarm.d.ts +0 -6
  167. package/aws/infra/canaries/canary-alarm.js +0 -26
  168. package/aws/infra/canaries/canary-parameters.js +0 -3
  169. package/aws/infra/canaries/canary-role.d.ts +0 -6
  170. package/aws/infra/canaries/canary-role.js +0 -46
  171. package/aws/infra/canaries/canary.d.ts +0 -8
  172. package/aws/infra/canaries/canary.js +0 -32
  173. package/aws/infra/canaries/database-canary.d.ts +0 -18
  174. package/aws/infra/canaries/database-canary.js +0 -55
  175. package/aws/infra/canaries/database-checker.d.ts +0 -21
  176. package/aws/infra/canaries/database-checker.js +0 -109
  177. package/aws/infra/canaries/url-canary.d.ts +0 -19
  178. package/aws/infra/canaries/url-canary.js +0 -46
  179. package/aws/infra/canaries/url-checker.d.ts +0 -46
  180. package/aws/infra/canaries/url-checker.js +0 -238
  181. package/aws/infra/documentation.d.ts +0 -56
  182. package/aws/infra/documentation.js +0 -95
  183. package/aws/infra/scheduler.d.ts +0 -12
  184. package/aws/infra/scheduler.js +0 -31
  185. package/aws/infra/security-rule.d.ts +0 -12
  186. package/aws/infra/security-rule.js +0 -39
  187. package/aws/infra/sqs-integration.d.ts +0 -7
  188. package/aws/infra/sqs-integration.js +0 -93
  189. package/aws/infra/sqs-queue.d.ts +0 -16
  190. package/aws/infra/sqs-queue.js +0 -130
  191. package/aws/infra/stack/lambda-configs.d.ts +0 -72
  192. package/aws/infra/stack/lambda-configs.js +0 -93
  193. package/aws/infra/stack/monitoredfunction.d.ts +0 -84
  194. package/aws/infra/stack/monitoredfunction.js +0 -135
  195. package/aws/infra/stack/rest_apis.d.ts +0 -41
  196. package/aws/infra/stack/rest_apis.js +0 -185
  197. package/aws/infra/stack/stack-checking-aspect.d.ts +0 -21
  198. package/aws/infra/stack/stack-checking-aspect.js +0 -174
  199. package/aws/infra/stack/stack.d.ts +0 -44
  200. package/aws/infra/stack/stack.js +0 -60
  201. package/aws/infra/stack/subscription.d.ts +0 -17
  202. package/aws/infra/stack/subscription.js +0 -41
  203. package/aws/infra/usage-plans.d.ts +0 -15
  204. package/aws/infra/usage-plans.js +0 -42
  205. package/aws/runtime/apikey.d.ts +0 -2
  206. package/aws/runtime/apikey.js +0 -13
  207. package/aws/runtime/digitraffic-integration-response.d.ts +0 -8
  208. package/aws/runtime/digitraffic-integration-response.js +0 -26
  209. package/aws/runtime/environment.d.ts +0 -1
  210. package/aws/runtime/environment.js +0 -12
  211. package/aws/runtime/messaging.d.ts +0 -10
  212. package/aws/runtime/messaging.js +0 -31
  213. package/aws/runtime/s3.d.ts +0 -2
  214. package/aws/runtime/s3.js +0 -30
  215. package/aws/runtime/secrets/dbsecret.d.ts +0 -54
  216. package/aws/runtime/secrets/dbsecret.js +0 -96
  217. package/aws/runtime/secrets/proxy-holder.d.ts +0 -9
  218. package/aws/runtime/secrets/proxy-holder.js +0 -26
  219. package/aws/runtime/secrets/rds-holder.d.ts +0 -9
  220. package/aws/runtime/secrets/rds-holder.js +0 -26
  221. package/aws/runtime/secrets/secret-holder.d.ts +0 -26
  222. package/aws/runtime/secrets/secret-holder.js +0 -73
  223. package/aws/runtime/secrets/secret.d.ts +0 -8
  224. package/aws/runtime/secrets/secret.js +0 -43
  225. package/aws/types/errors.d.ts +0 -4
  226. package/aws/types/errors.js +0 -9
  227. package/aws/types/lambda-response.d.ts +0 -12
  228. package/aws/types/lambda-response.js +0 -28
  229. package/aws/types/mediatypes.js +0 -15
  230. package/aws/types/model-with-reference.js +0 -3
  231. package/aws/types/proxytypes.d.ts +0 -26
  232. package/aws/types/proxytypes.js +0 -3
  233. package/aws/types/tags.d.ts +0 -2
  234. package/aws/types/tags.js +0 -7
  235. package/database/cached.d.ts +0 -7
  236. package/database/cached.js +0 -32
  237. package/database/database.d.ts +0 -19
  238. package/database/database.js +0 -62
  239. package/database/last-updated.d.ts +0 -16
  240. package/database/last-updated.js +0 -54
  241. package/database/models.js +0 -3
  242. package/index.d.ts +0 -1
  243. package/index.js +0 -18
  244. package/marine/id_utils.d.ts +0 -3
  245. package/marine/id_utils.js +0 -33
  246. package/marine/rtz.d.ts +0 -48
  247. package/marine/rtz.js +0 -3
  248. package/test/asserter.d.ts +0 -11
  249. package/test/asserter.js +0 -45
  250. package/test/db-testutils.d.ts +0 -2
  251. package/test/db-testutils.js +0 -31
  252. package/test/httpserver.d.ts +0 -18
  253. package/test/httpserver.js +0 -67
  254. package/test/secret.d.ts +0 -3
  255. package/test/secret.js +0 -25
  256. package/test/secrets-manager.d.ts +0 -9
  257. package/test/secrets-manager.js +0 -59
  258. package/test/testutils.d.ts +0 -12
  259. package/test/testutils.js +0 -44
  260. package/types/input-error.d.ts +0 -2
  261. package/types/input-error.js +0 -7
  262. package/types/language.d.ts +0 -5
  263. package/types/language.js +0 -10
  264. package/types/traffictype.d.ts +0 -8
  265. package/types/traffictype.js +0 -13
  266. package/types/validator.d.ts +0 -4
  267. package/types/validator.js +0 -14
  268. package/utils/api-model.d.ts +0 -87
  269. package/utils/api-model.js +0 -129
  270. package/utils/base64.d.ts +0 -12
  271. package/utils/base64.js +0 -21
  272. package/utils/date-utils.d.ts +0 -17
  273. package/utils/date-utils.js +0 -34
  274. package/utils/geojson-types.d.ts +0 -14
  275. package/utils/geojson-types.js +0 -18
  276. package/utils/geometry.d.ts +0 -36
  277. package/utils/geometry.js +0 -140
  278. package/utils/retry.d.ts +0 -13
  279. package/utils/retry.js +0 -50
  280. package/utils/slack.d.ts +0 -5
  281. package/utils/slack.js +0 -25
  282. package/utils/utils.d.ts +0 -30
  283. 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,9 @@
1
+ import {APIGateway} from "aws-sdk";
2
+
3
+ export function getApiKeyFromAPIGateway(keyId: string): Promise<APIGateway.Types.ApiKey> {
4
+ const agw = new APIGateway();
5
+ return agw.getApiKey({
6
+ apiKey: keyId,
7
+ includeValue: true,
8
+ }).promise();
9
+ }
@@ -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,9 @@
1
+ export function envValue(key: string): string {
2
+ const value = process.env[key];
3
+
4
+ if (value == null) {
5
+ throw new Error(`Missing environment value ${key}`);
6
+ }
7
+
8
+ return value;
9
+ }
@@ -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
+ }