@oneuptime/common 12.0.21 → 12.0.23
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/Index.ts +4 -0
- package/Models/DatabaseModels/NetworkDevice.ts +44 -0
- package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +564 -0
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +38 -0
- package/Models/DatabaseModels/UserTwoFactorBackupCode.ts +262 -0
- package/Server/API/UserTwoFactorBackupCodeAPI.ts +258 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddNetworkDeviceAutoImportRule.ts +60 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddUserTwoFactorBackupCode.ts +63 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789200000000-AddSysObjectIdPatternToAutoImportRule.ts +26 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789300000000-AddAutoApplyVendorHealthTemplate.ts +26 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +917 -0
- package/Server/Services/NetworkDeviceAutoImportRuleService.ts +181 -0
- package/Server/Services/UserService.ts +42 -0
- package/Server/Services/UserTwoFactorBackupCodeService.ts +355 -0
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +39 -1
- package/Server/Utils/TwoFactorBackupCode.ts +266 -0
- package/Tests/Server/API/UserAuthenticationAPI.test.ts +16 -3
- package/Tests/Server/API/UserTwoFactorAuthAdminAPI.test.ts +18 -2
- package/Tests/Server/API/UserTwoFactorBackupCodeAPI.test.ts +1390 -0
- package/Tests/Server/Services/AutoImportScanCredentialSelect.test.ts +205 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +933 -0
- package/Tests/Server/Services/UserAuthenticationService.test.ts +23 -1
- package/Tests/Server/Services/UserTwoFactorAuthAdmin.test.ts +21 -0
- package/Tests/Server/Services/UserTwoFactorBackupCodeAdminSurface.test.ts +919 -0
- package/Tests/Server/Services/UserTwoFactorBackupCodeService.test.ts +862 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +97 -1
- package/Tests/Server/Utils/TwoFactorBackupCode.test.ts +475 -0
- package/Tests/Utils/NetworkDiscovery/AutoImportRuleMatcher.test.ts +516 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.test.ts +272 -0
- package/Tests/Utils/NetworkDiscovery/ScanTargetUtil.test.ts +164 -0
- package/Types/Email/EmailTemplateType.ts +2 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +84 -0
- package/Types/Permission.ts +47 -0
- package/Types/UserAuthenticationStatus.ts +16 -0
- package/Utils/NetworkDiscovery/AutoImportRuleMatcher.ts +257 -0
- package/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.ts +183 -0
- package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +95 -0
- package/Utils/NetworkDiscovery/DiscoveryImportEligibility.ts +66 -0
- package/Utils/NetworkDiscovery/ScanTargetUtil.ts +82 -0
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +45 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +589 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +28 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserTwoFactorBackupCode.js +277 -0
- package/build/dist/Models/DatabaseModels/UserTwoFactorBackupCode.js.map +1 -0
- package/build/dist/Server/API/UserTwoFactorBackupCodeAPI.js +201 -0
- package/build/dist/Server/API/UserTwoFactorBackupCodeAPI.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddNetworkDeviceAutoImportRule.js +35 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddNetworkDeviceAutoImportRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddUserTwoFactorBackupCode.js +46 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddUserTwoFactorBackupCode.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789200000000-AddSysObjectIdPatternToAutoImportRule.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789200000000-AddSysObjectIdPatternToAutoImportRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789300000000-AddAutoApplyVendorHealthTemplate.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789300000000-AddAutoApplyVendorHealthTemplate.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +711 -0
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js +155 -0
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js.map +1 -0
- package/build/dist/Server/Services/UserService.js +39 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/UserTwoFactorBackupCodeService.js +327 -0
- package/build/dist/Server/Services/UserTwoFactorBackupCodeService.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +26 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/TwoFactorBackupCode.js +269 -0
- package/build/dist/Server/Utils/TwoFactorBackupCode.js.map +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js +2 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +24 -0
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
- package/build/dist/Types/Permission.js +41 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js +149 -0
- package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js +121 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +75 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveryImportEligibility.js +59 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveryImportEligibility.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +60 -0
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import AutoImportRuleMatcher, {
|
|
2
|
+
AutoImportHostEvaluation,
|
|
3
|
+
AutoImportRuleCandidate,
|
|
4
|
+
} from "../../../Utils/NetworkDiscovery/AutoImportRuleMatcher";
|
|
5
|
+
import { DiscoveredNetworkDevice } from "../../../Models/DatabaseModels/NetworkDeviceDiscoveryScan";
|
|
6
|
+
import { describe, expect, it } from "@jest/globals";
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Contract under test — which discovered hosts an auto-import rule claims,
|
|
10
|
+
* and what the rule set as a whole says about one host (issue #3378).
|
|
11
|
+
*
|
|
12
|
+
* The semantics worth pinning are the ones an operator relies on without
|
|
13
|
+
* reading the code: populated conditions AND within a rule, an empty rule
|
|
14
|
+
* matches NOTHING (not everything), OR is expressed as multiple rules, an
|
|
15
|
+
* exclusion rule vetoes unconditionally, and a ping-only host needs an
|
|
16
|
+
* explicit opt-in before any import rule may claim it.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
function host(
|
|
20
|
+
overrides: Partial<DiscoveredNetworkDevice> = {},
|
|
21
|
+
): DiscoveredNetworkDevice {
|
|
22
|
+
return {
|
|
23
|
+
ipAddress: "10.0.0.5",
|
|
24
|
+
sysName: "switch-WB1678-01",
|
|
25
|
+
sysDescr: "Cisco IOS Software, C2960X",
|
|
26
|
+
...overrides,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("AutoImportRuleMatcher.ruleMatchesHost", () => {
|
|
31
|
+
/*
|
|
32
|
+
* The site-rule precedent: an all-wildcard rule is a typo, not a
|
|
33
|
+
* match-everything. A rule that claimed every host on an empty form would
|
|
34
|
+
* import whole subnets the moment it was saved half-finished.
|
|
35
|
+
*/
|
|
36
|
+
it("matches nothing when the rule has no conditions", () => {
|
|
37
|
+
expect(AutoImportRuleMatcher.ruleMatchesHost({}, host())).toBe(false);
|
|
38
|
+
expect(
|
|
39
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
40
|
+
{ ipMatchTarget: "", sysNamePattern: "", sysDescrPattern: "" },
|
|
41
|
+
host(),
|
|
42
|
+
),
|
|
43
|
+
).toBe(false);
|
|
44
|
+
// Whitespace-only conditions are "not configured", not conditions.
|
|
45
|
+
expect(
|
|
46
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
47
|
+
{ ipMatchTarget: " ", sysNamePattern: " " },
|
|
48
|
+
host(),
|
|
49
|
+
),
|
|
50
|
+
).toBe(false);
|
|
51
|
+
// Nor does the ping-only opt-in count as a condition by itself.
|
|
52
|
+
expect(
|
|
53
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
54
|
+
{ includePingOnlyHosts: true },
|
|
55
|
+
host(),
|
|
56
|
+
),
|
|
57
|
+
).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("matches on a single populated condition", () => {
|
|
61
|
+
expect(
|
|
62
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
63
|
+
{ ipMatchTarget: "10.0.0.0/24" },
|
|
64
|
+
host(),
|
|
65
|
+
),
|
|
66
|
+
).toBe(true);
|
|
67
|
+
expect(
|
|
68
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
69
|
+
{ ipMatchTarget: "10.0.1.0/24" },
|
|
70
|
+
host(),
|
|
71
|
+
),
|
|
72
|
+
).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("ANDs populated conditions: ip and sysName must both hold", () => {
|
|
76
|
+
const rule: AutoImportRuleCandidate = {
|
|
77
|
+
ipMatchTarget: "10.0.0.0/24",
|
|
78
|
+
sysNamePattern: "WB1678",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
expect(AutoImportRuleMatcher.ruleMatchesHost(rule, host())).toBe(true);
|
|
82
|
+
|
|
83
|
+
// Right subnet, wrong name.
|
|
84
|
+
expect(
|
|
85
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
86
|
+
rule,
|
|
87
|
+
host({ sysName: "switch-XX9999-01" }),
|
|
88
|
+
),
|
|
89
|
+
).toBe(false);
|
|
90
|
+
|
|
91
|
+
// Right name, wrong subnet.
|
|
92
|
+
expect(
|
|
93
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
94
|
+
rule,
|
|
95
|
+
host({ ipAddress: "192.168.1.5" }),
|
|
96
|
+
),
|
|
97
|
+
).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/*
|
|
101
|
+
* Pattern semantics are RulePatternMatchUtil's, shared with the label and
|
|
102
|
+
* owner rules: a plain string is an unanchored case-insensitive regex
|
|
103
|
+
* ("contains"), and a '*' glob works too — the syntax the neighbouring
|
|
104
|
+
* site rules taught operators (OneUptime/oneuptime#2940).
|
|
105
|
+
*/
|
|
106
|
+
it("matches sysName as an unanchored substring regex", () => {
|
|
107
|
+
expect(
|
|
108
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
109
|
+
{ sysNamePattern: "WB1678" },
|
|
110
|
+
host({ sysName: "switch-WB1678-01" }),
|
|
111
|
+
),
|
|
112
|
+
).toBe(true);
|
|
113
|
+
expect(
|
|
114
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
115
|
+
{ sysNamePattern: "WB1678" },
|
|
116
|
+
host({ sysName: "switch-XX9999-01" }),
|
|
117
|
+
),
|
|
118
|
+
).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("matches sysName as a '*' glob as well", () => {
|
|
122
|
+
expect(
|
|
123
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
124
|
+
{ sysNamePattern: "*WB1678*" },
|
|
125
|
+
host({ sysName: "switch-WB1678-01" }),
|
|
126
|
+
),
|
|
127
|
+
).toBe(true);
|
|
128
|
+
expect(
|
|
129
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
130
|
+
{ sysNamePattern: "*WB1678*" },
|
|
131
|
+
host({ sysName: "switch-XX9999-01" }),
|
|
132
|
+
),
|
|
133
|
+
).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("matches sysDescr with the same regex-first-glob-fallback semantics", () => {
|
|
137
|
+
expect(
|
|
138
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
139
|
+
{ sysDescrPattern: "C2960X" },
|
|
140
|
+
host(),
|
|
141
|
+
),
|
|
142
|
+
).toBe(true);
|
|
143
|
+
expect(
|
|
144
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
145
|
+
{ sysDescrPattern: "*IOS*" },
|
|
146
|
+
host(),
|
|
147
|
+
),
|
|
148
|
+
).toBe(true);
|
|
149
|
+
expect(
|
|
150
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
151
|
+
{ sysDescrPattern: "JunOS" },
|
|
152
|
+
host(),
|
|
153
|
+
),
|
|
154
|
+
).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
/*
|
|
158
|
+
* A ping-only host has no SNMP identity, so a host with no sysName at all
|
|
159
|
+
* is routine — and a configured name pattern must read it as "does not
|
|
160
|
+
* match", never as "nothing to check".
|
|
161
|
+
*/
|
|
162
|
+
it("never matches a configured sysNamePattern against a host with no sysName", () => {
|
|
163
|
+
expect(
|
|
164
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
165
|
+
{ sysNamePattern: "WB1678" },
|
|
166
|
+
host({ sysName: undefined }),
|
|
167
|
+
),
|
|
168
|
+
).toBe(false);
|
|
169
|
+
expect(
|
|
170
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
171
|
+
{ sysNamePattern: "*WB1678*" },
|
|
172
|
+
host({ sysName: undefined }),
|
|
173
|
+
),
|
|
174
|
+
).toBe(false);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("the sysObjectID condition", () => {
|
|
178
|
+
/*
|
|
179
|
+
* The vendor fingerprint: sysObjectID is the vendor's registered
|
|
180
|
+
* enterprise OID, so "any Cisco device" is a glob over the 1.3.6.1.4.1.9
|
|
181
|
+
* arc. Deliberately NOT the free-text semantics of the other two
|
|
182
|
+
* pattern conditions — OID matching is a literal-dot whole-string glob,
|
|
183
|
+
* or an arc-prefix test when the pattern has no '*'.
|
|
184
|
+
*/
|
|
185
|
+
it("matches a vendor by enterprise-arc glob and counts as a condition on its own", () => {
|
|
186
|
+
const ciscoRule: AutoImportRuleCandidate = {
|
|
187
|
+
sysObjectIdPattern: "1.3.6.1.4.1.9.*",
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
expect(
|
|
191
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
192
|
+
ciscoRule,
|
|
193
|
+
host({ sysObjectId: "1.3.6.1.4.1.9.1.1745" }),
|
|
194
|
+
),
|
|
195
|
+
).toBe(true);
|
|
196
|
+
|
|
197
|
+
// A Juniper box sits under a different arc and stays out.
|
|
198
|
+
expect(
|
|
199
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
200
|
+
ciscoRule,
|
|
201
|
+
host({ sysObjectId: "1.3.6.1.4.1.2636.1.1.1.2.137" }),
|
|
202
|
+
),
|
|
203
|
+
).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
/*
|
|
207
|
+
* The regression that forced OID-aware matching: under the free-text
|
|
208
|
+
* matcher's regex-FIRST semantics, "1.3.6.1.4.1.9.*" compiled as an
|
|
209
|
+
* unanchored regex where every '.' matches anything, so "any Cisco
|
|
210
|
+
* device" also imported (or, as an exclusion, silently vetoed) every
|
|
211
|
+
* enterprise whose number merely STARTS with 9 — Nokia (94), 990,
|
|
212
|
+
* 9148... In an OID, dots are dots and 9 is not 94.
|
|
213
|
+
*/
|
|
214
|
+
it("never bleeds into sibling enterprise arcs that share a digit prefix", () => {
|
|
215
|
+
const ciscoGlob: AutoImportRuleCandidate = {
|
|
216
|
+
sysObjectIdPattern: "1.3.6.1.4.1.9.*",
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
for (const foreignOid of [
|
|
220
|
+
"1.3.6.1.4.1.94.1.21", // Nokia
|
|
221
|
+
"1.3.6.1.4.1.990.2.1",
|
|
222
|
+
"1.3.6.1.4.1.9999.1.2",
|
|
223
|
+
]) {
|
|
224
|
+
expect(
|
|
225
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
226
|
+
ciscoGlob,
|
|
227
|
+
host({ sysObjectId: foreignOid }),
|
|
228
|
+
),
|
|
229
|
+
).toBe(false);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// And the same holds when the rule is an exclusion veto.
|
|
233
|
+
const excludeCisco: AutoImportRuleCandidate = {
|
|
234
|
+
sysObjectIdPattern: "1.3.6.1.4.1.9.*",
|
|
235
|
+
isExclusion: true,
|
|
236
|
+
};
|
|
237
|
+
const nokiaHost: DiscoveredNetworkDevice = host({
|
|
238
|
+
sysObjectId: "1.3.6.1.4.1.94.1.21",
|
|
239
|
+
});
|
|
240
|
+
const importAll: AutoImportRuleCandidate = {
|
|
241
|
+
ipMatchTarget: "10.0.0.0/8",
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const evaluation: AutoImportHostEvaluation =
|
|
245
|
+
AutoImportRuleMatcher.evaluateHost(
|
|
246
|
+
[excludeCisco, importAll],
|
|
247
|
+
nokiaHost,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
expect(evaluation.excludedByRule).toBeUndefined();
|
|
251
|
+
expect(evaluation.shouldImport).toBe(true);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("treats a starless pattern as an arc prefix on sub-identifier boundaries", () => {
|
|
255
|
+
const ciscoPrefix: AutoImportRuleCandidate = {
|
|
256
|
+
sysObjectIdPattern: "1.3.6.1.4.1.9",
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// The arc itself and everything under it.
|
|
260
|
+
expect(
|
|
261
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
262
|
+
ciscoPrefix,
|
|
263
|
+
host({ sysObjectId: "1.3.6.1.4.1.9" }),
|
|
264
|
+
),
|
|
265
|
+
).toBe(true);
|
|
266
|
+
expect(
|
|
267
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
268
|
+
ciscoPrefix,
|
|
269
|
+
host({ sysObjectId: "1.3.6.1.4.1.9.1.1745" }),
|
|
270
|
+
),
|
|
271
|
+
).toBe(true);
|
|
272
|
+
|
|
273
|
+
// Never the sibling arc that shares the digit prefix.
|
|
274
|
+
expect(
|
|
275
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
276
|
+
ciscoPrefix,
|
|
277
|
+
host({ sysObjectId: "1.3.6.1.4.1.94.1.21" }),
|
|
278
|
+
),
|
|
279
|
+
).toBe(false);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("tolerates the leading dot some agents prefix OIDs with, on either side", () => {
|
|
283
|
+
expect(
|
|
284
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
285
|
+
{ sysObjectIdPattern: "1.3.6.1.4.1.9.*" },
|
|
286
|
+
host({ sysObjectId: ".1.3.6.1.4.1.9.1.1745" }),
|
|
287
|
+
),
|
|
288
|
+
).toBe(true);
|
|
289
|
+
|
|
290
|
+
expect(
|
|
291
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
292
|
+
{ sysObjectIdPattern: ".1.3.6.1.4.1.9" },
|
|
293
|
+
host({ sysObjectId: "1.3.6.1.4.1.9.1.1745" }),
|
|
294
|
+
),
|
|
295
|
+
).toBe(true);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("a glob cannot match mid-OID — the pattern must cover the whole arc", () => {
|
|
299
|
+
expect(
|
|
300
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
301
|
+
{ sysObjectIdPattern: "2.1.1.*" },
|
|
302
|
+
host({ sysObjectId: "1.3.6.1.4.1.14988.2.1.1.5" }),
|
|
303
|
+
),
|
|
304
|
+
).toBe(false);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("ANDs with the other conditions like any criterion", () => {
|
|
308
|
+
const rule: AutoImportRuleCandidate = {
|
|
309
|
+
ipMatchTarget: "10.0.0.0/24",
|
|
310
|
+
sysObjectIdPattern: "1.3.6.1.4.1.9.*",
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
expect(
|
|
314
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
315
|
+
rule,
|
|
316
|
+
host({ sysObjectId: "1.3.6.1.4.1.9.1.1745" }),
|
|
317
|
+
),
|
|
318
|
+
).toBe(true);
|
|
319
|
+
|
|
320
|
+
expect(
|
|
321
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
322
|
+
rule,
|
|
323
|
+
host({
|
|
324
|
+
ipAddress: "192.168.7.1",
|
|
325
|
+
sysObjectId: "1.3.6.1.4.1.9.1.1745",
|
|
326
|
+
}),
|
|
327
|
+
),
|
|
328
|
+
).toBe(false);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
/*
|
|
332
|
+
* Ping-only hosts and rows stored by pre-sysObjectId probes carry no
|
|
333
|
+
* sysObjectId at all. A configured vendor condition must not match a
|
|
334
|
+
* host whose vendor is unknown — the same missing-value rule the
|
|
335
|
+
* sysName/sysDescr patterns follow.
|
|
336
|
+
*/
|
|
337
|
+
it("never matches a host whose scan did not carry sysObjectId", () => {
|
|
338
|
+
expect(
|
|
339
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
340
|
+
{ sysObjectIdPattern: "1.3.6.1.4.1.9.*" },
|
|
341
|
+
host({ sysObjectId: undefined }),
|
|
342
|
+
),
|
|
343
|
+
).toBe(false);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
describe("pattern subject clamping", () => {
|
|
348
|
+
/*
|
|
349
|
+
* The jsonb these hosts come from is the probe's payload stored
|
|
350
|
+
* verbatim and unbounded, while patterns run through JavaScript's
|
|
351
|
+
* backtracking regex engine — so subjects are clamped to 500 characters
|
|
352
|
+
* before matching. Real SNMP DisplayStrings top out at 255 octets;
|
|
353
|
+
* only hostile or corrupt payloads are longer, and the clamp bounds
|
|
354
|
+
* what any pattern can be made to chew on.
|
|
355
|
+
*/
|
|
356
|
+
it("does not see pattern evidence past the 500-character clamp", () => {
|
|
357
|
+
const sysDescrWithLateEvidence: string =
|
|
358
|
+
"x".repeat(520) + "NEEDLE" + "x".repeat(74);
|
|
359
|
+
expect(sysDescrWithLateEvidence).toHaveLength(600);
|
|
360
|
+
|
|
361
|
+
expect(
|
|
362
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
363
|
+
{ sysDescrPattern: "NEEDLE" },
|
|
364
|
+
host({ sysDescr: sysDescrWithLateEvidence }),
|
|
365
|
+
),
|
|
366
|
+
).toBe(false);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("still sees evidence that sits before the clamp", () => {
|
|
370
|
+
const sysDescrWithEarlyEvidence: string =
|
|
371
|
+
"x".repeat(100) + "NEEDLE" + "x".repeat(494);
|
|
372
|
+
expect(sysDescrWithEarlyEvidence).toHaveLength(600);
|
|
373
|
+
|
|
374
|
+
expect(
|
|
375
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
376
|
+
{ sysDescrPattern: "NEEDLE" },
|
|
377
|
+
host({ sysDescr: sysDescrWithEarlyEvidence }),
|
|
378
|
+
),
|
|
379
|
+
).toBe(true);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
describe("the ping-only gate", () => {
|
|
384
|
+
/*
|
|
385
|
+
* An SNMP credential typo makes a whole subnet report as ping-only; a
|
|
386
|
+
* rule silently importing hundreds of half-identified hosts on that typo
|
|
387
|
+
* is the failure mode the default-closed gate exists to prevent.
|
|
388
|
+
*/
|
|
389
|
+
it("does not let an import rule claim a ping-only host by default", () => {
|
|
390
|
+
expect(
|
|
391
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
392
|
+
{ ipMatchTarget: "10.0.0.0/24" },
|
|
393
|
+
host({ snmpReachable: false }),
|
|
394
|
+
),
|
|
395
|
+
).toBe(false);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("lets an import rule claim a ping-only host after the explicit opt-in", () => {
|
|
399
|
+
expect(
|
|
400
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
401
|
+
{ ipMatchTarget: "10.0.0.0/24", includePingOnlyHosts: true },
|
|
402
|
+
host({ snmpReachable: false }),
|
|
403
|
+
),
|
|
404
|
+
).toBe(true);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
/*
|
|
408
|
+
* Scans stored before snmpReachable existed carry undefined, and every
|
|
409
|
+
* host on them answered SNMP by construction — so legacy rows must keep
|
|
410
|
+
* importing without the opt-in.
|
|
411
|
+
*/
|
|
412
|
+
it("reads an undefined snmpReachable (legacy scan) as SNMP-reachable", () => {
|
|
413
|
+
expect(
|
|
414
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
415
|
+
{ ipMatchTarget: "10.0.0.0/24" },
|
|
416
|
+
host({ snmpReachable: undefined }),
|
|
417
|
+
),
|
|
418
|
+
).toBe(true);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// A veto is a veto whatever the host answered with.
|
|
422
|
+
it("lets an exclusion rule match a ping-only host regardless of the opt-in", () => {
|
|
423
|
+
expect(
|
|
424
|
+
AutoImportRuleMatcher.ruleMatchesHost(
|
|
425
|
+
{ ipMatchTarget: "10.0.0.0/24", isExclusion: true },
|
|
426
|
+
host({ snmpReachable: false }),
|
|
427
|
+
),
|
|
428
|
+
).toBe(true);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
describe("AutoImportRuleMatcher.evaluateHost", () => {
|
|
434
|
+
const matchingRuleA: AutoImportRuleCandidate = {
|
|
435
|
+
ipMatchTarget: "10.0.0.0/24",
|
|
436
|
+
};
|
|
437
|
+
const matchingRuleB: AutoImportRuleCandidate = {
|
|
438
|
+
sysNamePattern: "WB1678",
|
|
439
|
+
};
|
|
440
|
+
const nonMatchingRule: AutoImportRuleCandidate = {
|
|
441
|
+
ipMatchTarget: "192.168.0.0/16",
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
// OR across rules: any import rule matching is enough.
|
|
445
|
+
it("imports when any import rule matches, listing exactly the ones that did", () => {
|
|
446
|
+
const evaluation: AutoImportHostEvaluation =
|
|
447
|
+
AutoImportRuleMatcher.evaluateHost(
|
|
448
|
+
[nonMatchingRule, matchingRuleA, matchingRuleB],
|
|
449
|
+
host(),
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
expect(evaluation.shouldImport).toBe(true);
|
|
453
|
+
expect(evaluation.matchedRules).toEqual([matchingRuleA, matchingRuleB]);
|
|
454
|
+
expect(evaluation.excludedByRule).toBeUndefined();
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
/*
|
|
458
|
+
* The veto, and its explainability contract: the answer is "excluded by
|
|
459
|
+
* rule R" with NO matched rules listed, so a dry run reads as one decision
|
|
460
|
+
* rather than "matched A and B and then didn't".
|
|
461
|
+
*/
|
|
462
|
+
it("lets an exclusion rule veto even when import rules match", () => {
|
|
463
|
+
const exclusionRule: AutoImportRuleCandidate = {
|
|
464
|
+
ipMatchTarget: "10.0.0.5",
|
|
465
|
+
isExclusion: true,
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const evaluation: AutoImportHostEvaluation =
|
|
469
|
+
AutoImportRuleMatcher.evaluateHost(
|
|
470
|
+
[matchingRuleA, exclusionRule, matchingRuleB],
|
|
471
|
+
host(),
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
expect(evaluation.shouldImport).toBe(false);
|
|
475
|
+
expect(evaluation.matchedRules).toEqual([]);
|
|
476
|
+
expect(evaluation.excludedByRule).toBe(exclusionRule);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it("does not veto through an exclusion rule the host does not match", () => {
|
|
480
|
+
const irrelevantExclusion: AutoImportRuleCandidate = {
|
|
481
|
+
ipMatchTarget: "172.16.0.0/12",
|
|
482
|
+
isExclusion: true,
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const evaluation: AutoImportHostEvaluation =
|
|
486
|
+
AutoImportRuleMatcher.evaluateHost(
|
|
487
|
+
[matchingRuleA, irrelevantExclusion],
|
|
488
|
+
host(),
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
expect(evaluation.shouldImport).toBe(true);
|
|
492
|
+
expect(evaluation.matchedRules).toEqual([matchingRuleA]);
|
|
493
|
+
expect(evaluation.excludedByRule).toBeUndefined();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("does not import when no rules exist", () => {
|
|
497
|
+
const evaluation: AutoImportHostEvaluation =
|
|
498
|
+
AutoImportRuleMatcher.evaluateHost([], host());
|
|
499
|
+
|
|
500
|
+
expect(evaluation.shouldImport).toBe(false);
|
|
501
|
+
expect(evaluation.matchedRules).toEqual([]);
|
|
502
|
+
expect(evaluation.excludedByRule).toBeUndefined();
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it("does not import when only exclusion rules exist", () => {
|
|
506
|
+
const evaluation: AutoImportHostEvaluation =
|
|
507
|
+
AutoImportRuleMatcher.evaluateHost(
|
|
508
|
+
[{ ipMatchTarget: "10.0.0.0/24", isExclusion: true }],
|
|
509
|
+
host(),
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
expect(evaluation.shouldImport).toBe(false);
|
|
513
|
+
expect(evaluation.matchedRules).toEqual([]);
|
|
514
|
+
expect(evaluation.excludedByRule).toBeDefined();
|
|
515
|
+
});
|
|
516
|
+
});
|