@oneuptime/common 12.0.28 → 12.0.29

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 (182) hide show
  1. package/Models/DatabaseModels/DashboardDomain.ts +43 -0
  2. package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
  3. package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
  4. package/Server/API/DashboardDomainAPI.ts +74 -0
  5. package/Server/API/MicrosoftTeamsAPI.ts +81 -180
  6. package/Server/API/StatusPageDomainAPI.ts +74 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  10. package/Server/Middleware/ProjectAuthorization.ts +6 -1
  11. package/Server/Services/DashboardDomainService.ts +131 -0
  12. package/Server/Services/LogPipelineProcessorService.ts +88 -0
  13. package/Server/Services/MonitorTemplateService.ts +70 -11
  14. package/Server/Services/StatusPageDomainService.ts +163 -3
  15. package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
  16. package/Server/Types/Domain.ts +17 -0
  17. package/Server/Utils/APIKey/AccessPermission.ts +24 -0
  18. package/Server/Utils/Greenlock/Greenlock.ts +156 -58
  19. package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
  20. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
  21. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
  22. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
  23. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
  24. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
  25. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
  26. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
  27. package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
  28. package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
  29. package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
  30. package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
  31. package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
  32. package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
  33. package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
  34. package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
  35. package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
  36. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
  37. package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
  38. package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
  39. package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
  40. package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
  41. package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
  42. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
  43. package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
  44. package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
  45. package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
  46. package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
  47. package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
  48. package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
  49. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
  50. package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
  51. package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
  52. package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
  53. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
  54. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
  55. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
  56. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
  57. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
  58. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
  59. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
  60. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
  61. package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
  62. package/Tests/Types/API/HTTPResponse.test.ts +175 -0
  63. package/Tests/Types/API/Route.test.ts +36 -4
  64. package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
  65. package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
  66. package/Tests/Types/DateLocalTimeString.test.ts +259 -0
  67. package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
  68. package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
  69. package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
  70. package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
  71. package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
  72. package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
  73. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
  74. package/Tests/UI/Utils/Navigation.test.ts +82 -0
  75. package/Tests/Utils/Array.test.ts +100 -0
  76. package/Tests/Utils/CertificateReissue.test.ts +225 -0
  77. package/Tests/Utils/Grok/Grok.test.ts +492 -0
  78. package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
  79. package/Types/API/Route.ts +20 -5
  80. package/Types/Date.ts +143 -13
  81. package/Types/Rum/SessionReplay.ts +62 -1
  82. package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
  83. package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
  84. package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
  85. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
  86. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
  87. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
  88. package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
  89. package/UI/Utils/Navigation.ts +42 -0
  90. package/Utils/Array.ts +37 -0
  91. package/Utils/CertificateReissue.ts +128 -0
  92. package/Utils/Grok/Grok.ts +461 -0
  93. package/Utils/Grok/GrokPatterns.ts +118 -0
  94. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
  95. package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
  96. package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
  97. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
  98. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  99. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
  100. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  101. package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
  102. package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
  103. package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
  104. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  105. package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
  106. package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
  107. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
  108. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
  109. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
  110. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js.map +1 -0
  111. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  112. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  113. package/build/dist/Server/Middleware/ProjectAuthorization.js +6 -1
  114. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  115. package/build/dist/Server/Services/DashboardDomainService.js +104 -0
  116. package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
  117. package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
  118. package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
  119. package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
  120. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  121. package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
  122. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  123. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
  124. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
  125. package/build/dist/Server/Types/Domain.js +16 -0
  126. package/build/dist/Server/Types/Domain.js.map +1 -1
  127. package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
  128. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  129. package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
  130. package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
  131. package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
  132. package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
  133. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
  134. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
  135. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
  136. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
  137. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
  138. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  139. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
  140. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  141. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
  142. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
  143. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
  144. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  145. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
  146. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  147. package/build/dist/Types/API/Route.js +16 -5
  148. package/build/dist/Types/API/Route.js.map +1 -1
  149. package/build/dist/Types/Date.js +115 -13
  150. package/build/dist/Types/Date.js.map +1 -1
  151. package/build/dist/Types/Rum/SessionReplay.js +38 -0
  152. package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
  153. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
  154. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
  155. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
  156. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  157. package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
  158. package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
  159. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
  160. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  161. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
  162. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  163. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
  164. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  165. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
  166. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
  167. package/build/dist/UI/Utils/Navigation.js +30 -0
  168. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  169. package/build/dist/Utils/Array.js +25 -0
  170. package/build/dist/Utils/Array.js.map +1 -1
  171. package/build/dist/Utils/CertificateReissue.js +97 -0
  172. package/build/dist/Utils/CertificateReissue.js.map +1 -0
  173. package/build/dist/Utils/Grok/Grok.js +324 -0
  174. package/build/dist/Utils/Grok/Grok.js.map +1 -0
  175. package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
  176. package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
  177. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
  178. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
  179. package/package.json +1 -1
  180. package/UI/Utils/JsonWebToken.ts +0 -14
  181. package/build/dist/UI/Utils/JsonWebToken.js +0 -10
  182. package/build/dist/UI/Utils/JsonWebToken.js.map +0 -1
@@ -0,0 +1,492 @@
1
+ import BadDataException from "../../../Types/Exception/BadDataException";
2
+ import {
3
+ CompiledGrokPattern,
4
+ GrokValue,
5
+ MAX_GROK_INPUT_LENGTH,
6
+ clearGrokCompileCache,
7
+ compileGrokPattern,
8
+ compileGrokPatternCached,
9
+ matchGrokPattern,
10
+ parseWithGrokPattern,
11
+ } from "../../../Utils/Grok/Grok";
12
+ import GrokPatterns, {
13
+ getGrokPatternNames,
14
+ } from "../../../Utils/Grok/GrokPatterns";
15
+
16
+ /*
17
+ * The grok engine behind the LogPipeline GrokParser processor
18
+ * (OneUptime/oneuptime#2515). It runs once per ingested log record
19
+ * against text the customer's users control, so both halves matter: it
20
+ * has to extract what a user's pattern says it extracts, and it has to
21
+ * refuse anything that would let a pattern or an input run away with the
22
+ * ingest worker.
23
+ */
24
+
25
+ describe("Grok - reference forms", () => {
26
+ it("captures a named field", () => {
27
+ expect(parseWithGrokPattern("%{WORD:verb}", "GET /index.html")).toEqual({
28
+ verb: "GET",
29
+ });
30
+ });
31
+
32
+ it("matches without capturing when no field name is given", () => {
33
+ expect(
34
+ parseWithGrokPattern("%{WORD} %{WORD:second}", "hello world"),
35
+ ).toEqual({ second: "world" });
36
+ });
37
+
38
+ it("mixes literal regex with grok references", () => {
39
+ expect(
40
+ parseWithGrokPattern(
41
+ "^\\[%{LOGLEVEL:level}\\] %{GREEDYDATA:message}$",
42
+ "[ERROR] disk is full",
43
+ ),
44
+ ).toEqual({ level: "ERROR", message: "disk is full" });
45
+ });
46
+
47
+ it("is unanchored, like Logstash", () => {
48
+ expect(
49
+ parseWithGrokPattern("%{IPV4:client_ip}", "request from 10.0.1.5 ok"),
50
+ ).toEqual({ client_ip: "10.0.1.5" });
51
+ });
52
+
53
+ it("returns null when the pattern does not match", () => {
54
+ expect(
55
+ parseWithGrokPattern("%{IPV4:client_ip}", "no address here"),
56
+ ).toBeNull();
57
+ });
58
+
59
+ it("returns an empty object for a pattern that matches but captures nothing", () => {
60
+ expect(parseWithGrokPattern("%{WORD}", "hello")).toEqual({});
61
+ });
62
+
63
+ it("expands nested references, keeping the sub-pattern's own field names", () => {
64
+ // SYSLOGBASE names timestamp / logsource / program / pid internally.
65
+ const parsed: Record<string, GrokValue> | null = parseWithGrokPattern(
66
+ "%{SYSLOGBASE} %{GREEDYDATA:message}",
67
+ "Oct 12 14:03:21 web-01 sshd[1234]: connection closed",
68
+ );
69
+
70
+ expect(parsed).toEqual({
71
+ timestamp: "Oct 12 14:03:21",
72
+ logsource: "web-01",
73
+ program: "sshd",
74
+ pid: "1234",
75
+ message: "connection closed",
76
+ });
77
+ });
78
+ });
79
+
80
+ describe("Grok - type coercion", () => {
81
+ it("coerces int / integer / long to a number", () => {
82
+ expect(parseWithGrokPattern("%{NUMBER:status:int}", "status 404")).toEqual({
83
+ status: 404,
84
+ });
85
+ expect(
86
+ parseWithGrokPattern("%{NUMBER:status:integer}", "status 404"),
87
+ ).toEqual({ status: 404 });
88
+ expect(parseWithGrokPattern("%{NUMBER:bytes:long}", "bytes 1024")).toEqual({
89
+ bytes: 1024,
90
+ });
91
+ });
92
+
93
+ it("coerces float / double / number to a number", () => {
94
+ expect(
95
+ parseWithGrokPattern("%{NUMBER:duration:float}", "took 12.5ms"),
96
+ ).toEqual({ duration: 12.5 });
97
+ expect(
98
+ parseWithGrokPattern("%{NUMBER:duration:double}", "took 12.5ms"),
99
+ ).toEqual({ duration: 12.5 });
100
+ });
101
+
102
+ it("keeps a negative number's sign", () => {
103
+ expect(
104
+ parseWithGrokPattern("%{NUMBER:delta:float}", "delta -3.25"),
105
+ ).toEqual({ delta: -3.25 });
106
+ });
107
+
108
+ it("coerces booleans from the usual spellings", () => {
109
+ expect(parseWithGrokPattern("%{WORD:cached:boolean}", "true")).toEqual({
110
+ cached: true,
111
+ });
112
+ expect(parseWithGrokPattern("%{WORD:cached:bool}", "no")).toEqual({
113
+ cached: false,
114
+ });
115
+ expect(parseWithGrokPattern("%{WORD:cached:boolean}", "1")).toEqual({
116
+ cached: true,
117
+ });
118
+ });
119
+
120
+ it("leaves a value that is not recognisably boolean as text", () => {
121
+ expect(parseWithGrokPattern("%{WORD:cached:boolean}", "maybe")).toEqual({
122
+ cached: "maybe",
123
+ });
124
+ });
125
+
126
+ it("defaults to string when no type is given", () => {
127
+ expect(parseWithGrokPattern("%{NUMBER:status}", "status 404")).toEqual({
128
+ status: "404",
129
+ });
130
+ });
131
+
132
+ it("rejects an unknown type at compile time", () => {
133
+ expect(() => {
134
+ return compileGrokPattern("%{WORD:foo:datetime}");
135
+ }).toThrow(BadDataException);
136
+ });
137
+ });
138
+
139
+ describe("Grok - capture semantics", () => {
140
+ it("drops captures that did not participate in the match", () => {
141
+ const parsed: Record<string, GrokValue> | null = parseWithGrokPattern(
142
+ "%{WORD:verb}(?: %{NUMBER:status:int})?",
143
+ "GET",
144
+ );
145
+
146
+ expect(parsed).toEqual({ verb: "GET" });
147
+ expect(parsed).not.toHaveProperty("status");
148
+ });
149
+
150
+ it("drops empty captures", () => {
151
+ expect(parseWithGrokPattern("a%{SPACE:gap}b", "ab")).toEqual({});
152
+ });
153
+
154
+ it("keeps the first branch that actually matched when a name repeats", () => {
155
+ expect(
156
+ parseWithGrokPattern(
157
+ "(?:%{IPV4:host}|%{HOSTNAME:host})",
158
+ "web-01 online",
159
+ ),
160
+ ).toEqual({ host: "web-01" });
161
+ });
162
+
163
+ it("does not leak internal group names", () => {
164
+ const parsed: Record<string, GrokValue> | null = parseWithGrokPattern(
165
+ "%{WORD:verb}",
166
+ "GET",
167
+ );
168
+
169
+ expect(Object.keys(parsed as Record<string, GrokValue>)).toEqual(["verb"]);
170
+ });
171
+ });
172
+
173
+ describe("Grok - pattern library", () => {
174
+ it("parses a combined Apache access log line", () => {
175
+ const line: string =
176
+ '10.0.0.7 - frank [10/Oct/2023:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "http://example.com/start.html" "Mozilla/5.0"';
177
+
178
+ expect(parseWithGrokPattern("%{COMBINEDAPACHELOG}", line)).toEqual({
179
+ clientip: "10.0.0.7",
180
+ ident: "-",
181
+ auth: "frank",
182
+ timestamp: "10/Oct/2023:13:55:36 -0700",
183
+ verb: "GET",
184
+ request: "/apache_pb.gif",
185
+ httpversion: "1.0",
186
+ response: "200",
187
+ bytes: "2326",
188
+ referrer: '"http://example.com/start.html"',
189
+ agent: '"Mozilla/5.0"',
190
+ });
191
+ });
192
+
193
+ it("parses an ISO8601 timestamped application line", () => {
194
+ expect(
195
+ parseWithGrokPattern(
196
+ "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{JAVACLASS:logger} - %{GREEDYDATA:message}",
197
+ "2023-10-10T13:55:36.123Z WARN com.example.svc.Worker - queue is backing up",
198
+ ),
199
+ ).toEqual({
200
+ timestamp: "2023-10-10T13:55:36.123Z",
201
+ level: "WARN",
202
+ logger: "com.example.svc.Worker",
203
+ message: "queue is backing up",
204
+ });
205
+ });
206
+
207
+ it("matches IPv6 addresses", () => {
208
+ expect(
209
+ parseWithGrokPattern(
210
+ "%{IP:client_ip}",
211
+ "from 2001:db8::8a2e:370:7334 ok",
212
+ ),
213
+ ).toEqual({ client_ip: "2001:db8::8a2e:370:7334" });
214
+ });
215
+
216
+ it("matches email addresses, UUIDs and MAC addresses", () => {
217
+ expect(
218
+ parseWithGrokPattern(
219
+ "%{EMAILADDRESS:email}",
220
+ "user: jane.doe@example.com",
221
+ ),
222
+ ).toEqual({ email: "jane.doe@example.com" });
223
+
224
+ expect(
225
+ parseWithGrokPattern(
226
+ "%{UUID:request_id}",
227
+ "rid=123e4567-e89b-12d3-a456-426614174000",
228
+ ),
229
+ ).toEqual({ request_id: "123e4567-e89b-12d3-a456-426614174000" });
230
+
231
+ expect(parseWithGrokPattern("%{MAC:mac}", "mac 00:1A:2B:3C:4D:5E")).toEqual(
232
+ {
233
+ mac: "00:1A:2B:3C:4D:5E",
234
+ },
235
+ );
236
+ });
237
+
238
+ it("matches quoted strings and URIs", () => {
239
+ expect(
240
+ parseWithGrokPattern("%{QS:quoted}", 'said "hello world" once'),
241
+ ).toEqual({ quoted: '"hello world"' });
242
+
243
+ expect(
244
+ parseWithGrokPattern(
245
+ "%{URI:url}",
246
+ "fetching https://example.com/a/b?c=1 now",
247
+ ),
248
+ ).toEqual({ url: "https://example.com/a/b?c=1" });
249
+ });
250
+
251
+ it("rejects an out-of-range IPv4 octet", () => {
252
+ expect(parseWithGrokPattern("%{IPV4:ip}", "999.1.1.1")).toBeNull();
253
+ });
254
+
255
+ it("every library definition compiles on its own", () => {
256
+ for (const name of getGrokPatternNames()) {
257
+ expect(() => {
258
+ return compileGrokPattern(`%{${name}}`);
259
+ }).not.toThrow();
260
+ }
261
+ });
262
+
263
+ it("no library definition uses a capturing group", () => {
264
+ for (const [name, definition] of Object.entries(GrokPatterns)) {
265
+ /*
266
+ * The engine reads captures back by group NAME and injects those
267
+ * names itself, so a bare "(" in a definition would burn a capture
268
+ * slot on every record for nothing. Escapes and character classes
269
+ * are stripped first - "\\(" and "[(]" are literal parentheses.
270
+ */
271
+ const withoutEscapes: string = definition.replace(/\\./g, "");
272
+ const withoutCharacterClasses: string = withoutEscapes.replace(
273
+ /\[[^\]]*\]/g,
274
+ "",
275
+ );
276
+ const capturing: RegExpMatchArray | null =
277
+ withoutCharacterClasses.match(/\((?!\?)/);
278
+
279
+ expect([name, capturing]).toEqual([name, null]);
280
+ }
281
+ });
282
+ });
283
+
284
+ describe("Grok - compile errors are actionable", () => {
285
+ it("rejects an empty pattern", () => {
286
+ expect(() => {
287
+ return compileGrokPattern(" ");
288
+ }).toThrow("Grok pattern cannot be empty.");
289
+ });
290
+
291
+ it("rejects an unknown pattern name", () => {
292
+ expect(() => {
293
+ return compileGrokPattern("%{NOSUCHPATTERN:foo}");
294
+ }).toThrow('Unknown grok pattern "%{NOSUCHPATTERN}"');
295
+ });
296
+
297
+ it("rejects an invalid field name", () => {
298
+ expect(() => {
299
+ return compileGrokPattern("%{WORD:my field}");
300
+ }).toThrow(BadDataException);
301
+ });
302
+
303
+ it("rejects invalid raw regex", () => {
304
+ expect(() => {
305
+ return compileGrokPattern("%{WORD:verb} (unclosed");
306
+ }).toThrow(BadDataException);
307
+ });
308
+
309
+ it("rejects an over-long pattern", () => {
310
+ expect(() => {
311
+ return compileGrokPattern(`%{WORD:verb}${"x".repeat(4000)}`);
312
+ }).toThrow("cannot be longer than");
313
+ });
314
+ });
315
+
316
+ describe("Grok - runaway guards", () => {
317
+ it("rejects a circular pattern reference instead of hanging", () => {
318
+ expect(() => {
319
+ return compileGrokPattern("%{A_PATTERN:x}", {
320
+ A_PATTERN: "%{B_PATTERN}",
321
+ B_PATTERN: "%{A_PATTERN}",
322
+ });
323
+ }).toThrow("refers to itself");
324
+ });
325
+
326
+ it("rejects a self-referencing pattern", () => {
327
+ expect(() => {
328
+ return compileGrokPattern("%{SELF:x}", { SELF: "a%{SELF}" });
329
+ }).toThrow("refers to itself");
330
+ });
331
+
332
+ it("rejects nesting deeper than the depth ceiling", () => {
333
+ const deep: Record<string, string> = {};
334
+
335
+ for (let i: number = 0; i < 30; i++) {
336
+ deep[`LEVEL${i}`] = `%{LEVEL${i + 1}}`;
337
+ }
338
+
339
+ deep["LEVEL30"] = "x";
340
+
341
+ expect(() => {
342
+ return compileGrokPattern("%{LEVEL0:x}", deep);
343
+ }).toThrow("levels deep");
344
+ });
345
+
346
+ it("rejects a pattern that expands past the source-length ceiling", () => {
347
+ /*
348
+ * Each level doubles, so this blows the size ceiling long before the
349
+ * depth one - the guard that has to hold for a pattern which is
350
+ * shallow but explosive.
351
+ */
352
+ const doubling: Record<string, string> = { WIDE10: "x" };
353
+
354
+ for (let i: number = 0; i < 10; i++) {
355
+ doubling[`WIDE${i}`] = `%{WIDE${i + 1}}%{WIDE${i + 1}}`;
356
+ }
357
+
358
+ expect(() => {
359
+ return compileGrokPattern("%{WIDE0:x}", doubling);
360
+ }).toThrow(BadDataException);
361
+ });
362
+
363
+ it("rejects a pattern capturing more fields than the ceiling allows", () => {
364
+ const many: string = new Array(120)
365
+ .fill(null)
366
+ .map((_unused: null, index: number) => {
367
+ return `%{WORD:field_${index}}`;
368
+ })
369
+ .join(" ");
370
+
371
+ expect(() => {
372
+ return compileGrokPattern(many);
373
+ }).toThrow("captures more than");
374
+ });
375
+
376
+ it("refuses to run against an input over the length ceiling", () => {
377
+ const compiled: CompiledGrokPattern = compileGrokPattern("%{WORD:first}");
378
+
379
+ expect(matchGrokPattern(compiled, "hello")).toEqual({ first: "hello" });
380
+ expect(
381
+ matchGrokPattern(compiled, "a".repeat(MAX_GROK_INPUT_LENGTH + 1)),
382
+ ).toBeNull();
383
+ });
384
+
385
+ it("returns null for an empty input", () => {
386
+ const compiled: CompiledGrokPattern =
387
+ compileGrokPattern("%{GREEDYDATA:all}");
388
+
389
+ expect(matchGrokPattern(compiled, "")).toBeNull();
390
+ });
391
+ });
392
+
393
+ describe("Grok - compiled patterns are reusable", () => {
394
+ it("does not carry match state between calls", () => {
395
+ const compiled: CompiledGrokPattern = compileGrokPattern("%{WORD:word}");
396
+
397
+ expect(compiled.regex.global).toBe(false);
398
+ expect(compiled.regex.sticky).toBe(false);
399
+
400
+ for (let i: number = 0; i < 5; i++) {
401
+ expect(matchGrokPattern(compiled, "alpha beta")).toEqual({
402
+ word: "alpha",
403
+ });
404
+ }
405
+ });
406
+ });
407
+
408
+ describe("Grok - library patterns stay linear on hostile input", () => {
409
+ /*
410
+ * These run once per ingested record against text a customer's users
411
+ * control, and a backtracking regex cannot be interrupted once it
412
+ * starts. Every library definition is written so each repetition
413
+ * consumes at least one character; this is the check that nobody adds
414
+ * an `(a*)*`-shaped one later. Thresholds are deliberately loose - the
415
+ * failure mode being caught is seconds-to-forever, not milliseconds.
416
+ */
417
+ const HOSTILE_LINES: Array<[string, string]> = [
418
+ ["%{COMBINEDAPACHELOG}", `10.0.0.7 - - [${"a".repeat(4000)}`],
419
+ ["%{URI:url}", `https://example.com/${"a/".repeat(2000)}`],
420
+ ["%{PATH:path}", `/${"a/".repeat(2000)}`],
421
+ ["%{IPV6:ip}", `${"1:".repeat(2000)}z`],
422
+ ["%{QS:quoted}", `"${"\\".repeat(2000)}`],
423
+ ["%{EMAILADDRESS:email}", `${"a.".repeat(2000)}@`],
424
+ ["%{TIMESTAMP_ISO8601:ts}", `${"2023-10-10T13:55:".repeat(500)}x`],
425
+ ["%{HOSTNAME:host}", `${"a.".repeat(2000)}!`],
426
+ ];
427
+
428
+ it.each(HOSTILE_LINES)(
429
+ "%s finishes quickly on input built to make it backtrack",
430
+ (pattern: string, input: string) => {
431
+ const compiled: CompiledGrokPattern = compileGrokPattern(pattern);
432
+ const startedAt: number = Date.now();
433
+
434
+ matchGrokPattern(compiled, input);
435
+
436
+ expect(Date.now() - startedAt).toBeLessThan(2000);
437
+ },
438
+ );
439
+
440
+ it("stays quick across a whole batch of records", () => {
441
+ const compiled: CompiledGrokPattern = compileGrokPattern(
442
+ "%{COMBINEDAPACHELOG}",
443
+ );
444
+ const line: string =
445
+ '10.0.0.7 - frank [10/Oct/2023:13:55:36 -0700] "GET /a HTTP/1.0" 200 23 "-" "-"';
446
+
447
+ const startedAt: number = Date.now();
448
+
449
+ for (let i: number = 0; i < 2000; i++) {
450
+ matchGrokPattern(compiled, line);
451
+ }
452
+
453
+ expect(Date.now() - startedAt).toBeLessThan(2000);
454
+ });
455
+ });
456
+
457
+ describe("Grok - compile cache", () => {
458
+ beforeEach(() => {
459
+ clearGrokCompileCache();
460
+ });
461
+
462
+ it("returns the same compiled instance for the same pattern text", () => {
463
+ const first: CompiledGrokPattern = compileGrokPatternCached("%{WORD:verb}");
464
+ const second: CompiledGrokPattern =
465
+ compileGrokPatternCached("%{WORD:verb}");
466
+
467
+ expect(second).toBe(first);
468
+ });
469
+
470
+ it("re-throws for a pattern that failed to compile, without recompiling", () => {
471
+ expect(() => {
472
+ return compileGrokPatternCached("%{NOSUCHPATTERN}");
473
+ }).toThrow(BadDataException);
474
+
475
+ expect(() => {
476
+ return compileGrokPatternCached("%{NOSUCHPATTERN}");
477
+ }).toThrow(BadDataException);
478
+ });
479
+
480
+ it("stays bounded", () => {
481
+ for (let i: number = 0; i < 600; i++) {
482
+ compileGrokPatternCached(`%{WORD:field_${i}}`);
483
+ }
484
+
485
+ // The oldest entries were evicted, so this recompiles into a new object.
486
+ const recompiled: CompiledGrokPattern =
487
+ compileGrokPatternCached("%{WORD:field_0}");
488
+
489
+ expect(recompiled).not.toBe(compileGrokPatternCached("%{WORD:field_599}"));
490
+ expect(recompiled.captures).toHaveLength(1);
491
+ });
492
+ });