@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.
- package/Models/DatabaseModels/DashboardDomain.ts +43 -0
- package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
- package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
- package/Server/API/DashboardDomainAPI.ts +74 -0
- package/Server/API/MicrosoftTeamsAPI.ts +81 -180
- package/Server/API/StatusPageDomainAPI.ts +74 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/ProjectAuthorization.ts +6 -1
- package/Server/Services/DashboardDomainService.ts +131 -0
- package/Server/Services/LogPipelineProcessorService.ts +88 -0
- package/Server/Services/MonitorTemplateService.ts +70 -11
- package/Server/Services/StatusPageDomainService.ts +163 -3
- package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
- package/Server/Types/Domain.ts +17 -0
- package/Server/Utils/APIKey/AccessPermission.ts +24 -0
- package/Server/Utils/Greenlock/Greenlock.ts +156 -58
- package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
- package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
- package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
- package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
- package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
- package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
- package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
- package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
- package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
- package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
- package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
- package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
- package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
- package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
- package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
- package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
- package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
- package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
- package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
- package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
- package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
- package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
- package/Tests/Types/API/HTTPResponse.test.ts +175 -0
- package/Tests/Types/API/Route.test.ts +36 -4
- package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
- package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
- package/Tests/Types/DateLocalTimeString.test.ts +259 -0
- package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
- package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
- package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
- package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
- package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
- package/Tests/UI/Utils/Navigation.test.ts +82 -0
- package/Tests/Utils/Array.test.ts +100 -0
- package/Tests/Utils/CertificateReissue.test.ts +225 -0
- package/Tests/Utils/Grok/Grok.test.ts +492 -0
- package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
- package/Types/API/Route.ts +20 -5
- package/Types/Date.ts +143 -13
- package/Types/Rum/SessionReplay.ts +62 -1
- package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
- package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
- package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
- package/UI/Utils/Navigation.ts +42 -0
- package/Utils/Array.ts +37 -0
- package/Utils/CertificateReissue.ts +128 -0
- package/Utils/Grok/Grok.ts +461 -0
- package/Utils/Grok/GrokPatterns.ts +118 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
- package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
- package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
- package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +6 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/DashboardDomainService.js +104 -0
- package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
- package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
- package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
- package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
- package/build/dist/Server/Types/Domain.js +16 -0
- package/build/dist/Server/Types/Domain.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
- package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/Route.js +16 -5
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/Date.js +115 -13
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplay.js +38 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +30 -0
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/Utils/Array.js +25 -0
- package/build/dist/Utils/Array.js.map +1 -1
- package/build/dist/Utils/CertificateReissue.js +97 -0
- package/build/dist/Utils/CertificateReissue.js.map +1 -0
- package/build/dist/Utils/Grok/Grok.js +324 -0
- package/build/dist/Utils/Grok/Grok.js.map +1 -0
- package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
- package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/package.json +1 -1
- package/UI/Utils/JsonWebToken.ts +0 -14
- package/build/dist/UI/Utils/JsonWebToken.js +0 -10
- 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
|
+
});
|