@statuser/cli 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +448 -2
- package/dist/client.js +163 -0
- package/dist/client.js.map +1 -0
- package/dist/commander-i18n.js +52 -0
- package/dist/commander-i18n.js.map +1 -0
- package/dist/commands/api.js +104 -0
- package/dist/commands/api.js.map +1 -0
- package/dist/commands/auth.js +104 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/catalog.js +72 -0
- package/dist/commands/catalog.js.map +1 -0
- package/dist/commands/config.js +302 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/heartbeat.js +199 -0
- package/dist/commands/heartbeat.js.map +1 -0
- package/dist/commands/incidents.js +265 -0
- package/dist/commands/incidents.js.map +1 -0
- package/dist/commands/maintenance.js +211 -0
- package/dist/commands/maintenance.js.map +1 -0
- package/dist/commands/reports.js +170 -0
- package/dist/commands/reports.js.map +1 -0
- package/dist/commands/scenarios.js +361 -0
- package/dist/commands/scenarios.js.map +1 -0
- package/dist/commands/servers-add.js +152 -0
- package/dist/commands/servers-add.js.map +1 -0
- package/dist/commands/servers-checks.js +56 -0
- package/dist/commands/servers-checks.js.map +1 -0
- package/dist/commands/servers.js +424 -0
- package/dist/commands/servers.js.map +1 -0
- package/dist/config.js +107 -0
- package/dist/config.js.map +1 -0
- package/dist/confirm.js +34 -0
- package/dist/confirm.js.map +1 -0
- package/dist/context.js +27 -0
- package/dist/context.js.map +1 -0
- package/dist/duration.js +20 -0
- package/dist/duration.js.map +1 -0
- package/dist/errors.js +73 -0
- package/dist/errors.js.map +1 -0
- package/dist/files.js +36 -0
- package/dist/files.js.map +1 -0
- package/dist/generated/openapi.js +6 -0
- package/dist/generated/openapi.js.map +1 -0
- package/dist/i18n.js +757 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/json-error.js +10 -0
- package/dist/json-error.js.map +1 -0
- package/dist/output.js +43 -0
- package/dist/output.js.map +1 -0
- package/dist/scenarios/config.js +228 -0
- package/dist/scenarios/config.js.map +1 -0
- package/dist/scenarios/plan.js +159 -0
- package/dist/scenarios/plan.js.map +1 -0
- package/dist/servers-config/export.js +72 -0
- package/dist/servers-config/export.js.map +1 -0
- package/dist/servers-config/fields.js +172 -0
- package/dist/servers-config/fields.js.map +1 -0
- package/dist/servers-config/forbidden-host.js +75 -0
- package/dist/servers-config/forbidden-host.js.map +1 -0
- package/dist/servers-config/host-pattern.js +6 -0
- package/dist/servers-config/host-pattern.js.map +1 -0
- package/dist/servers-config/parse.js +260 -0
- package/dist/servers-config/parse.js.map +1 -0
- package/dist/servers-config/plan.js +492 -0
- package/dist/servers-config/plan.js.map +1 -0
- package/dist/servers-update.js +157 -0
- package/dist/servers-update.js.map +1 -0
- package/dist/status-page.js +15 -0
- package/dist/status-page.js.map +1 -0
- package/dist/text-length.js +8 -0
- package/dist/text-length.js.map +1 -0
- package/dist/write-failure.js +37 -0
- package/dist/write-failure.js.map +1 -0
- package/package.json +70 -4
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { fieldsFor, isSecretHeader, MANAGED_PROTOCOLS, } from './fields.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pure: the `servers` section for a project. Every entry is built from an
|
|
4
|
+
* allowlist of the fields its protocol uses — a GET response also carries
|
|
5
|
+
* leftovers of a previous protocol, masks and the heartbeat token, and none
|
|
6
|
+
* of that belongs in a file people commit.
|
|
7
|
+
*/
|
|
8
|
+
export function exportServers(servers) {
|
|
9
|
+
const result = {
|
|
10
|
+
entries: [],
|
|
11
|
+
headerVariables: [],
|
|
12
|
+
withSecrets: [],
|
|
13
|
+
pausedCount: 0,
|
|
14
|
+
scenarioCount: 0,
|
|
15
|
+
};
|
|
16
|
+
for (const server of [...servers].sort((a, b) => a.id - b.id)) {
|
|
17
|
+
if (server.protocol === 'scenario') {
|
|
18
|
+
result.scenarioCount += 1;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (!MANAGED_PROTOCOLS.includes(server.protocol)) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const protocol = server.protocol;
|
|
25
|
+
if (server.status === 'paused')
|
|
26
|
+
result.pausedCount += 1;
|
|
27
|
+
const entry = {
|
|
28
|
+
id: server.id,
|
|
29
|
+
name: server.name,
|
|
30
|
+
protocol,
|
|
31
|
+
};
|
|
32
|
+
const source = server;
|
|
33
|
+
for (const field of fieldsFor(protocol)) {
|
|
34
|
+
const value = source[field];
|
|
35
|
+
if (value === null || value === undefined)
|
|
36
|
+
continue;
|
|
37
|
+
if (field === 'body' && value === '')
|
|
38
|
+
continue;
|
|
39
|
+
if (field === 'headers') {
|
|
40
|
+
if (!Array.isArray(value) || !value.length)
|
|
41
|
+
continue;
|
|
42
|
+
entry.headers = value.map((header, index) => {
|
|
43
|
+
if (isSecretHeader(header.key)) {
|
|
44
|
+
const variable = headerVariable(server.id, header.key, index);
|
|
45
|
+
result.headerVariables.push(variable);
|
|
46
|
+
return { key: header.key, value: `$${variable}` };
|
|
47
|
+
}
|
|
48
|
+
// A literal value starting with `$` would read as a reference.
|
|
49
|
+
const literal = header.value.startsWith('$')
|
|
50
|
+
? `$${header.value}`
|
|
51
|
+
: header.value;
|
|
52
|
+
return { key: header.key, value: literal };
|
|
53
|
+
});
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
entry[field] = value;
|
|
57
|
+
}
|
|
58
|
+
if ((protocol === 'http' || protocol === 'keyword'
|
|
59
|
+
? server.http_auth_type !== 'none' && server.http_auth_type !== null
|
|
60
|
+
: false) ||
|
|
61
|
+
(protocol === 'llm' && server.llm_api_key_masked)) {
|
|
62
|
+
result.withSecrets.push({ id: server.id, name: server.name });
|
|
63
|
+
}
|
|
64
|
+
result.entries.push(entry);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
function headerVariable(serverId, header, index) {
|
|
69
|
+
const name = header.toUpperCase().replace(/[^A-Z0-9]+/g, '_');
|
|
70
|
+
return `STATUSER_${serverId}_${index}_${name}`.replace(/_+$/, '');
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/servers-config/export.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
export const MANAGED_PROTOCOLS = [
|
|
2
|
+
'http',
|
|
3
|
+
'keyword',
|
|
4
|
+
'ping',
|
|
5
|
+
'tcp',
|
|
6
|
+
'dns',
|
|
7
|
+
'heartbeat',
|
|
8
|
+
'llm',
|
|
9
|
+
];
|
|
10
|
+
// Mirrors the backend (`servers.service.ts`): which settings a protocol
|
|
11
|
+
// actually uses. A field outside this list is not exported, and a file that
|
|
12
|
+
// sets it is rejected — the backend would keep it without effect or reset it.
|
|
13
|
+
const SSL_DOMAIN = ['http', 'keyword', 'dns'];
|
|
14
|
+
const CONFIRM_DELAY = [
|
|
15
|
+
'http',
|
|
16
|
+
'keyword',
|
|
17
|
+
'ping',
|
|
18
|
+
'tcp',
|
|
19
|
+
'llm',
|
|
20
|
+
];
|
|
21
|
+
const LATENCY = ['http', 'keyword', 'tcp', 'llm'];
|
|
22
|
+
const HTTP_LIKE = ['http', 'keyword'];
|
|
23
|
+
/** Readable settings the file manages, by protocol. */
|
|
24
|
+
export function fieldsFor(protocol) {
|
|
25
|
+
const fields = ['description', 'check_interval'];
|
|
26
|
+
if (protocol !== 'heartbeat')
|
|
27
|
+
fields.push('host');
|
|
28
|
+
// DNS is checked from the backend through fixed resolvers: the server's
|
|
29
|
+
// timeout and regions are stored but change nothing.
|
|
30
|
+
if (protocol !== 'heartbeat' && protocol !== 'dns') {
|
|
31
|
+
fields.push('request_timeout', 'locations');
|
|
32
|
+
}
|
|
33
|
+
if (CONFIRM_DELAY.includes(protocol))
|
|
34
|
+
fields.push('incident_confirm_delay');
|
|
35
|
+
if (LATENCY.includes(protocol)) {
|
|
36
|
+
fields.push('is_latency_alert_enabled', 'latency_trigger_ms');
|
|
37
|
+
}
|
|
38
|
+
if (SSL_DOMAIN.includes(protocol)) {
|
|
39
|
+
fields.push('is_ssl_check', 'is_domain_check', 'is_blocklist_check');
|
|
40
|
+
}
|
|
41
|
+
if (HTTP_LIKE.includes(protocol)) {
|
|
42
|
+
fields.push('http_method', 'body', 'headers', 'http_auth_type', 'http_auth_username', 'is_follow_redirects', 'success_http_codes');
|
|
43
|
+
}
|
|
44
|
+
if (protocol === 'keyword')
|
|
45
|
+
fields.push('keyword', 'keyword_mode');
|
|
46
|
+
if (protocol === 'tcp')
|
|
47
|
+
fields.push('port');
|
|
48
|
+
if (protocol === 'dns')
|
|
49
|
+
fields.push('dns_record_types');
|
|
50
|
+
if (protocol === 'heartbeat')
|
|
51
|
+
fields.push('heartbeat_grace_interval');
|
|
52
|
+
if (protocol === 'llm')
|
|
53
|
+
fields.push('llm_provider', 'llm_model');
|
|
54
|
+
return fields;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Write-only settings: the API never returns them, so they are never
|
|
58
|
+
* exported, and a file that sets one sends it on every apply.
|
|
59
|
+
*/
|
|
60
|
+
export function secretFieldsFor(protocol) {
|
|
61
|
+
if (HTTP_LIKE.includes(protocol)) {
|
|
62
|
+
return ['http_auth_password', 'http_auth_token'];
|
|
63
|
+
}
|
|
64
|
+
if (protocol === 'llm')
|
|
65
|
+
return ['llm_api_key'];
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
const SET_FIELDS = new Set([
|
|
69
|
+
'locations',
|
|
70
|
+
'dns_record_types',
|
|
71
|
+
'success_http_codes',
|
|
72
|
+
]);
|
|
73
|
+
/**
|
|
74
|
+
* Comparable form of a value as the backend stores it: strings trimmed,
|
|
75
|
+
* an empty body equal to none, order-free lists sorted, success codes in
|
|
76
|
+
* lower case.
|
|
77
|
+
*/
|
|
78
|
+
export function normalize(field, value) {
|
|
79
|
+
if (field === 'body') {
|
|
80
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
81
|
+
}
|
|
82
|
+
if (field === 'headers') {
|
|
83
|
+
return Array.isArray(value)
|
|
84
|
+
? value.map((h) => ({
|
|
85
|
+
key: String(h.key).trim(),
|
|
86
|
+
value: String(h.value).trim(),
|
|
87
|
+
}))
|
|
88
|
+
: [];
|
|
89
|
+
}
|
|
90
|
+
if (SET_FIELDS.has(field) && Array.isArray(value)) {
|
|
91
|
+
const items = value.map((item) => String(item).trim());
|
|
92
|
+
return (field === 'success_http_codes'
|
|
93
|
+
? items.map((item) => item.toLowerCase())
|
|
94
|
+
: items).sort();
|
|
95
|
+
}
|
|
96
|
+
if (typeof value === 'string') {
|
|
97
|
+
const trimmed = value.trim();
|
|
98
|
+
return field === 'description' && !trimmed ? null : trimmed;
|
|
99
|
+
}
|
|
100
|
+
return value ?? null;
|
|
101
|
+
}
|
|
102
|
+
export function sameValue(field, a, b) {
|
|
103
|
+
return (JSON.stringify(normalize(field, a)) === JSON.stringify(normalize(field, b)));
|
|
104
|
+
}
|
|
105
|
+
// Header names whose values usually carry credentials.
|
|
106
|
+
// Wide on purpose: a value shown by mistake is a leak, a value hidden by
|
|
107
|
+
// mistake is one `statuser api` call away. `key` covers X-Access-Key,
|
|
108
|
+
// Ocp-Apim-Subscription-Key and the like.
|
|
109
|
+
const SECRET_HEADER = /authorization|auth|cookie|token|secret|passw|key|session|signature|credential|private/i;
|
|
110
|
+
export function isSecretHeader(name) {
|
|
111
|
+
return SECRET_HEADER.test(name);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The account-wide key the backend uses to reject a duplicate monitor
|
|
115
|
+
* (`ServersService.findDuplicateServer`). Two servers with equal keys
|
|
116
|
+
* cannot coexist, whatever their names and projects.
|
|
117
|
+
*/
|
|
118
|
+
export function duplicateKey(state) {
|
|
119
|
+
const protocol = state.protocol;
|
|
120
|
+
const str = (field) => String(normalize(field, state[field]) ?? '');
|
|
121
|
+
// JSON keeps the parts apart: with a plain separator `a|b` + `c` and
|
|
122
|
+
// `a` + `b|c` would collide.
|
|
123
|
+
if (protocol === 'heartbeat')
|
|
124
|
+
return JSON.stringify(['heartbeat', str('name')]);
|
|
125
|
+
if (protocol === 'llm') {
|
|
126
|
+
return JSON.stringify([
|
|
127
|
+
'llm',
|
|
128
|
+
str('host'),
|
|
129
|
+
str('llm_provider'),
|
|
130
|
+
str('llm_model'),
|
|
131
|
+
]);
|
|
132
|
+
}
|
|
133
|
+
const parts = [protocol, str('host')];
|
|
134
|
+
if (protocol === 'tcp')
|
|
135
|
+
parts.push(str('port'));
|
|
136
|
+
if (protocol === 'http' || protocol === 'keyword') {
|
|
137
|
+
parts.push(str('http_method'), str('body'));
|
|
138
|
+
}
|
|
139
|
+
if (protocol === 'keyword')
|
|
140
|
+
parts.push(str('keyword'), str('keyword_mode'));
|
|
141
|
+
return JSON.stringify(parts);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Whether the API would take `existing` for a duplicate of `next`
|
|
145
|
+
* (`findDuplicateServer`). Directed: a null port, method, keyword or mode
|
|
146
|
+
* of `next` is left out of the search, so it matches any value there.
|
|
147
|
+
* `duplicateKey` stays the test for "the identity changed".
|
|
148
|
+
*/
|
|
149
|
+
export function isDuplicateOf(next, existing) {
|
|
150
|
+
const protocol = next.protocol;
|
|
151
|
+
if (existing.protocol !== protocol)
|
|
152
|
+
return false;
|
|
153
|
+
if (protocol === 'heartbeat' || protocol === 'llm') {
|
|
154
|
+
return duplicateKey(next) === duplicateKey(existing);
|
|
155
|
+
}
|
|
156
|
+
const value = (state, field) => normalize(field, state[field]) ?? null;
|
|
157
|
+
const same = (field) => value(next, field) === value(existing, field);
|
|
158
|
+
const sameIfSet = (field) => value(next, field) === null || same(field);
|
|
159
|
+
if (!same('host'))
|
|
160
|
+
return false;
|
|
161
|
+
if (protocol === 'tcp' && !sameIfSet('port'))
|
|
162
|
+
return false;
|
|
163
|
+
if (protocol === 'http' || protocol === 'keyword') {
|
|
164
|
+
if (!sameIfSet('http_method') || !same('body'))
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if (protocol === 'keyword') {
|
|
168
|
+
return sameIfSet('keyword') && sameIfSet('keyword_mode');
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fields.js","sourceRoot":"","sources":["../../src/servers-config/fields.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { isIP } from 'node:net';
|
|
2
|
+
// A copy of the backend's host gate (`assertServerHostPublic`): `localhost`
|
|
3
|
+
// and `*.localhost`, and the address literals of `isForbiddenIp`
|
|
4
|
+
// (src/utils/assert-public-webhook-url.ts) are refused with
|
|
5
|
+
// `server_host_not_allowed`. Other names are not resolved here — a name
|
|
6
|
+
// resolves differently from the user's network than from ours, and a refusal
|
|
7
|
+
// we cannot be sure of would block a valid apply.
|
|
8
|
+
function ipv4ToInt(ip) {
|
|
9
|
+
const parts = ip.split('.').map(Number);
|
|
10
|
+
if (parts.length !== 4 || parts.some((n) => !(n >= 0 && n <= 255))) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return (((parts[0] << 24) >>> 0) + (parts[1] << 16) + (parts[2] << 8) + parts[3]);
|
|
14
|
+
}
|
|
15
|
+
function inCidrV4(ip, base, prefix) {
|
|
16
|
+
const ipInt = ipv4ToInt(ip);
|
|
17
|
+
const baseInt = ipv4ToInt(base);
|
|
18
|
+
if (ipInt === null || baseInt === null)
|
|
19
|
+
return false;
|
|
20
|
+
const mask = prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0;
|
|
21
|
+
return (ipInt & mask) === (baseInt & mask);
|
|
22
|
+
}
|
|
23
|
+
const FORBIDDEN_V4 = [
|
|
24
|
+
['0.0.0.0', 8],
|
|
25
|
+
['127.0.0.0', 8],
|
|
26
|
+
['10.0.0.0', 8],
|
|
27
|
+
['172.16.0.0', 12],
|
|
28
|
+
['192.168.0.0', 16],
|
|
29
|
+
['100.64.0.0', 10],
|
|
30
|
+
['169.254.0.0', 16],
|
|
31
|
+
];
|
|
32
|
+
function isForbiddenIPv4(ip) {
|
|
33
|
+
return FORBIDDEN_V4.some(([base, prefix]) => inCidrV4(ip, base, prefix));
|
|
34
|
+
}
|
|
35
|
+
function isForbiddenIPv6(ip) {
|
|
36
|
+
const s = ip.toLowerCase();
|
|
37
|
+
const dotted = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(s);
|
|
38
|
+
if (dotted)
|
|
39
|
+
return isForbiddenIPv4(dotted[1]);
|
|
40
|
+
const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(s);
|
|
41
|
+
if (hex) {
|
|
42
|
+
const high = parseInt(hex[1], 16);
|
|
43
|
+
const low = parseInt(hex[2], 16);
|
|
44
|
+
return isForbiddenIPv4([high >> 8, high & 0xff, low >> 8, low & 0xff].join('.'));
|
|
45
|
+
}
|
|
46
|
+
return (s === '::1' ||
|
|
47
|
+
s === '::' ||
|
|
48
|
+
s.startsWith('fc') ||
|
|
49
|
+
s.startsWith('fd') ||
|
|
50
|
+
/^fe[89ab]/.test(s));
|
|
51
|
+
}
|
|
52
|
+
/** True when the backend surely refuses the host as internal. */
|
|
53
|
+
export function isForbiddenHost(rawHost) {
|
|
54
|
+
const trimmed = rawHost.trim();
|
|
55
|
+
const withScheme = /^[a-z][a-z\d+.-]*:\/\//i.test(trimmed)
|
|
56
|
+
? trimmed
|
|
57
|
+
: `https://${trimmed}`;
|
|
58
|
+
let host;
|
|
59
|
+
try {
|
|
60
|
+
host = new URL(withScheme).hostname.toLowerCase();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
host = host.replace(/^\[/, '').replace(/\]$/, '');
|
|
66
|
+
if (host === 'localhost' || host.endsWith('.localhost'))
|
|
67
|
+
return true;
|
|
68
|
+
const version = isIP(host);
|
|
69
|
+
if (version === 4)
|
|
70
|
+
return isForbiddenIPv4(host);
|
|
71
|
+
if (version === 6)
|
|
72
|
+
return isForbiddenIPv6(host);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=forbidden-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forbidden-host.js","sourceRoot":"","sources":["../../src/servers-config/forbidden-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Copy of `urlPattern` in the backend (`utils/server-host-validators.ts`):
|
|
2
|
+
// the host of every protocol except heartbeat is checked against it. Keep
|
|
3
|
+
// the two equal — a stricter copy refuses valid files, a looser one lets
|
|
4
|
+
// apply stop half-way on the backend's 400.
|
|
5
|
+
export const HOST_PATTERN = /^(https?:\/\/)?((([a-zA-Z0-9\u00a1-\uffff-]+|xn--[a-zA-Z0-9-]+)\.)+(xn--[a-zA-Z0-9-]+|[a-zA-Z\u00a1-\uffff]{2,})|((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(\[(?:([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){3}|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){3}))|([0-9a-fA-F]{1,4}:){1,5}:((25[0-5]|(2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){3}))\]))(?::(6553[0-5]|655[0-2]\d|65[0-4]\d{2}|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}))?(\/[^\s#?]*)?(\?[^\s#]*)?(#[^\s]*)?$/;
|
|
6
|
+
//# sourceMappingURL=host-pattern.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-pattern.js","sourceRoot":"","sources":["../../src/servers-config/host-pattern.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,0EAA0E;AAC1E,yEAAyE;AACzE,4CAA4C;AAC5C,MAAM,CAAC,MAAM,YAAY,GACvB,giCAAgiC,CAAC"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { t } from '../i18n.js';
|
|
2
|
+
import { HOST_PATTERN } from './host-pattern.js';
|
|
3
|
+
import { textLength } from '../text-length.js';
|
|
4
|
+
import { resolveEnvValue } from '../scenarios/config.js';
|
|
5
|
+
import { fieldsFor, MANAGED_PROTOCOLS, secretFieldsFor, } from './fields.js';
|
|
6
|
+
const LOCATIONS = ['msk-1', 'spb-1', 'ala-1', 'nyc-1', 'ams-1'];
|
|
7
|
+
const DNS_TYPES = [
|
|
8
|
+
'A',
|
|
9
|
+
'AAAA',
|
|
10
|
+
'CNAME',
|
|
11
|
+
'MX',
|
|
12
|
+
'TXT',
|
|
13
|
+
'NS',
|
|
14
|
+
'SOA',
|
|
15
|
+
'PTR',
|
|
16
|
+
'SRV',
|
|
17
|
+
];
|
|
18
|
+
const ENUMS = {
|
|
19
|
+
http_method: ['head', 'get', 'options', 'post', 'put', 'patch'],
|
|
20
|
+
keyword_mode: ['present', 'absent'],
|
|
21
|
+
http_auth_type: ['none', 'basic', 'bearer'],
|
|
22
|
+
llm_provider: ['openai_compatible', 'anthropic_compatible'],
|
|
23
|
+
};
|
|
24
|
+
// API bounds (`create-server.dto.ts`); checked here so a value the API
|
|
25
|
+
// rejects never stops apply after earlier writes.
|
|
26
|
+
const YEAR = 31_536_000;
|
|
27
|
+
const RANGES = {
|
|
28
|
+
check_interval: [60, 3600],
|
|
29
|
+
request_timeout: [1, 60],
|
|
30
|
+
incident_confirm_delay: [0, 3600],
|
|
31
|
+
latency_trigger_ms: [50, 60_000],
|
|
32
|
+
heartbeat_grace_interval: [0, YEAR],
|
|
33
|
+
port: [1, 65_535],
|
|
34
|
+
};
|
|
35
|
+
const BOOLEANS = [
|
|
36
|
+
'is_latency_alert_enabled',
|
|
37
|
+
'is_ssl_check',
|
|
38
|
+
'is_domain_check',
|
|
39
|
+
'is_blocklist_check',
|
|
40
|
+
'is_follow_redirects',
|
|
41
|
+
];
|
|
42
|
+
// String fields and their `MaxLength`; `null` — no limit.
|
|
43
|
+
const STRINGS = {
|
|
44
|
+
host: 300,
|
|
45
|
+
description: 500,
|
|
46
|
+
body: null,
|
|
47
|
+
keyword: 256,
|
|
48
|
+
http_auth_username: 256,
|
|
49
|
+
llm_model: 128,
|
|
50
|
+
};
|
|
51
|
+
const SECRET_MAX = {
|
|
52
|
+
http_auth_password: 4096,
|
|
53
|
+
http_auth_token: 4096,
|
|
54
|
+
llm_api_key: 512,
|
|
55
|
+
};
|
|
56
|
+
const NAME_MAX = 150;
|
|
57
|
+
// What `sanitizeHeaders` keeps: an RFC 7230 token as the name and no
|
|
58
|
+
// control characters in the value. Anything else the backend drops silently,
|
|
59
|
+
// so a file with it would wipe the server's headers on apply.
|
|
60
|
+
const HEADER_NAME = /^[!#$%&'*+.^_`|~0-9a-zA-Z-]+$/;
|
|
61
|
+
// eslint-disable-next-line no-control-regex
|
|
62
|
+
const HEADER_CONTROL = /[\x00-\x08\x0a-\x1f\x7f]/;
|
|
63
|
+
// HTTP credentials allow no control characters at all (`server-http-auth.ts`).
|
|
64
|
+
// eslint-disable-next-line no-control-regex
|
|
65
|
+
const AUTH_CONTROL = /[\x00-\x1f\x7f]/;
|
|
66
|
+
// `SUCCESS_HTTP_CODE_PATTERN` and its list size in the backend.
|
|
67
|
+
const SUCCESS_CODE = /^[1-5](xx|\d\d)$/;
|
|
68
|
+
const SUCCESS_CODES_MAX = 20;
|
|
69
|
+
/**
|
|
70
|
+
* Validates the `servers` section. Everything that can be checked without
|
|
71
|
+
* the API is checked here, before the first write: unknown or misplaced
|
|
72
|
+
* fields, types, ranges, lengths, header names, missing `$VAR`s.
|
|
73
|
+
*/
|
|
74
|
+
export function parseServersSection(raw, env, fail,
|
|
75
|
+
// A file finds an entry without an id by its name on every run; a single
|
|
76
|
+
// `servers add` creates and is done, so the name may be left out there.
|
|
77
|
+
options = {}) {
|
|
78
|
+
if (!Array.isArray(raw))
|
|
79
|
+
return fail(`servers: ${t.cfgArray}`);
|
|
80
|
+
const result = [];
|
|
81
|
+
for (const [index, item] of raw.entries()) {
|
|
82
|
+
const at = `servers[${index}]`;
|
|
83
|
+
if (typeof item !== 'object' || item === null || Array.isArray(item)) {
|
|
84
|
+
return fail(`${at}: ${t.cfgObject}`);
|
|
85
|
+
}
|
|
86
|
+
const entry = item;
|
|
87
|
+
const protocol = entry.protocol;
|
|
88
|
+
if (!MANAGED_PROTOCOLS.includes(protocol)) {
|
|
89
|
+
return fail(`${at}.protocol: ${t.cfgProtocol(MANAGED_PROTOCOLS.join(', '))}`);
|
|
90
|
+
}
|
|
91
|
+
if (entry.id !== undefined &&
|
|
92
|
+
(!Number.isInteger(entry.id) || entry.id <= 0)) {
|
|
93
|
+
return fail(`${at}.id: ${t.cfgPositiveInt}`);
|
|
94
|
+
}
|
|
95
|
+
const name = typeof entry.name === 'string' && entry.name.trim()
|
|
96
|
+
? entry.name.trim()
|
|
97
|
+
: null;
|
|
98
|
+
if (entry.name !== undefined && entry.name !== null && name === null) {
|
|
99
|
+
return fail(`${at}.name: ${t.cfgNonEmptyString}`);
|
|
100
|
+
}
|
|
101
|
+
if (name !== null && textLength(name) > NAME_MAX) {
|
|
102
|
+
return fail(`${at}.name: ${t.cfgMaxLength(NAME_MAX)}`);
|
|
103
|
+
}
|
|
104
|
+
if (name === null && entry.id === undefined && !options.allowUnnamed) {
|
|
105
|
+
return fail(`${at}: ${t.cfgNameOrId}`);
|
|
106
|
+
}
|
|
107
|
+
const readable = new Set(fieldsFor(protocol));
|
|
108
|
+
const secret = new Set(secretFieldsFor(protocol));
|
|
109
|
+
const fields = {};
|
|
110
|
+
const secrets = {};
|
|
111
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
112
|
+
if (key === 'id' || key === 'name' || key === 'protocol')
|
|
113
|
+
continue;
|
|
114
|
+
if (secret.has(key)) {
|
|
115
|
+
// An empty Basic password is legal; tokens and keys are not.
|
|
116
|
+
if (typeof value !== 'string' ||
|
|
117
|
+
(!value && key !== 'http_auth_password')) {
|
|
118
|
+
return fail(`${at}.${key}: ${t.cfgString}`);
|
|
119
|
+
}
|
|
120
|
+
let resolved = resolveEnvValue(value, `${at}.${key}`, env);
|
|
121
|
+
if (key === 'http_auth_token' || key === 'llm_api_key') {
|
|
122
|
+
// The backend trims a token or key and refuses an empty one: a
|
|
123
|
+
// blank LLM key would silently leave the check without the model.
|
|
124
|
+
resolved = resolved.trim();
|
|
125
|
+
if (!resolved)
|
|
126
|
+
return fail(`${at}.${key}: ${t.cfgString}`);
|
|
127
|
+
}
|
|
128
|
+
if (key !== 'llm_api_key' && AUTH_CONTROL.test(resolved)) {
|
|
129
|
+
return fail(`${at}.${key}: ${t.cfgControlChars}`);
|
|
130
|
+
}
|
|
131
|
+
// HTTP credentials are measured in UTF-16 units (`resolveHttpAuth`),
|
|
132
|
+
// the LLM key by `@MaxLength`.
|
|
133
|
+
const length = key === 'llm_api_key' ? textLength(resolved) : resolved.length;
|
|
134
|
+
if (length > SECRET_MAX[key]) {
|
|
135
|
+
return fail(`${at}.${key}: ${t.cfgMaxLength(SECRET_MAX[key])}`);
|
|
136
|
+
}
|
|
137
|
+
secrets[key] = resolved;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (!readable.has(key)) {
|
|
141
|
+
return fail(`${at}.${key}: ${t.cfgNotUsedBy(protocol)}`);
|
|
142
|
+
}
|
|
143
|
+
const problem = checkField(key, value, protocol);
|
|
144
|
+
if (problem)
|
|
145
|
+
return fail(`${at}.${key}: ${problem}`);
|
|
146
|
+
if (key === 'headers') {
|
|
147
|
+
const headers = value.map((h) => ({
|
|
148
|
+
key: h.key.trim(),
|
|
149
|
+
value: resolveEnvValue(h.value, `${at}.headers`, env).trim(),
|
|
150
|
+
}));
|
|
151
|
+
for (const header of headers) {
|
|
152
|
+
if (!HEADER_NAME.test(header.key)) {
|
|
153
|
+
return fail(`${at}.headers: ${t.cfgHeaderName(header.key)}`);
|
|
154
|
+
}
|
|
155
|
+
if (HEADER_CONTROL.test(header.value)) {
|
|
156
|
+
return fail(`${at}.headers: ${t.cfgHeaderValue(header.key)}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
fields.headers = headers;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (key === 'success_http_codes') {
|
|
163
|
+
// Stored as the API stores them, so the plan check sees `2xx`.
|
|
164
|
+
fields[key] = value.map((v) => v.trim().toLowerCase());
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
fields[key] = value;
|
|
168
|
+
}
|
|
169
|
+
result.push({
|
|
170
|
+
at,
|
|
171
|
+
id: entry.id,
|
|
172
|
+
name,
|
|
173
|
+
nameGiven: entry.name !== undefined,
|
|
174
|
+
protocol,
|
|
175
|
+
fields,
|
|
176
|
+
secrets,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
function checkField(key, value, protocol) {
|
|
182
|
+
const range = RANGES[key];
|
|
183
|
+
if (range) {
|
|
184
|
+
const max = key === 'check_interval' && protocol === 'heartbeat' ? YEAR : range[1];
|
|
185
|
+
return Number.isInteger(value) &&
|
|
186
|
+
value >= range[0] &&
|
|
187
|
+
value <= max
|
|
188
|
+
? null
|
|
189
|
+
: t.cfgRange(range[0], max);
|
|
190
|
+
}
|
|
191
|
+
if (BOOLEANS.includes(key)) {
|
|
192
|
+
return typeof value === 'boolean' ? null : t.cfgBoolean;
|
|
193
|
+
}
|
|
194
|
+
if (key in STRINGS) {
|
|
195
|
+
if (typeof value !== 'string')
|
|
196
|
+
return t.cfgString;
|
|
197
|
+
if (key === 'host' && !HOST_PATTERN.test(value.trim())) {
|
|
198
|
+
return t.cfgHostInvalid;
|
|
199
|
+
}
|
|
200
|
+
// The API trims these and refuses an empty result.
|
|
201
|
+
if ((key === 'keyword' || key === 'llm_model') && !value.trim()) {
|
|
202
|
+
return t.cfgString;
|
|
203
|
+
}
|
|
204
|
+
if (key === 'http_auth_username') {
|
|
205
|
+
// RFC 7617: the colon separates username and password.
|
|
206
|
+
if (value.includes(':'))
|
|
207
|
+
return t.cfgUsernameColon;
|
|
208
|
+
if (AUTH_CONTROL.test(value))
|
|
209
|
+
return t.cfgControlChars;
|
|
210
|
+
}
|
|
211
|
+
const max = STRINGS[key];
|
|
212
|
+
// The login goes to the API as written, and its length is checked
|
|
213
|
+
// there untrimmed.
|
|
214
|
+
// The login is also checked trimmed and in UTF-16 units when the
|
|
215
|
+
// authorization is resolved (`resolveHttpAuth`).
|
|
216
|
+
const tooLong = key === 'http_auth_username'
|
|
217
|
+
? textLength(value) > max || value.trim().length > max
|
|
218
|
+
: textLength(value.trim()) > max;
|
|
219
|
+
return typeof max === 'number' && tooLong ? t.cfgMaxLength(max) : null;
|
|
220
|
+
}
|
|
221
|
+
if (ENUMS[key]) {
|
|
222
|
+
return ENUMS[key].includes(value)
|
|
223
|
+
? null
|
|
224
|
+
: t.cfgOneOf(ENUMS[key].join(', '));
|
|
225
|
+
}
|
|
226
|
+
if (key === 'locations') {
|
|
227
|
+
// An empty list leaves the server without agents: it would never be checked.
|
|
228
|
+
return Array.isArray(value) &&
|
|
229
|
+
value.length > 0 &&
|
|
230
|
+
value.every((v) => LOCATIONS.includes(v))
|
|
231
|
+
? null
|
|
232
|
+
: t.cfgListOf(LOCATIONS.join(', '));
|
|
233
|
+
}
|
|
234
|
+
if (key === 'dns_record_types') {
|
|
235
|
+
return Array.isArray(value) &&
|
|
236
|
+
value.length > 0 &&
|
|
237
|
+
value.every((v) => DNS_TYPES.includes(v))
|
|
238
|
+
? null
|
|
239
|
+
: t.cfgListOf(DNS_TYPES.join(', '));
|
|
240
|
+
}
|
|
241
|
+
if (key === 'success_http_codes') {
|
|
242
|
+
return Array.isArray(value) &&
|
|
243
|
+
value.length >= 1 &&
|
|
244
|
+
value.length <= SUCCESS_CODES_MAX &&
|
|
245
|
+
value.every((v) => typeof v === 'string' && SUCCESS_CODE.test(v.trim().toLowerCase()))
|
|
246
|
+
? null
|
|
247
|
+
: t.cfgSuccessCodes(SUCCESS_CODES_MAX);
|
|
248
|
+
}
|
|
249
|
+
if (key === 'headers') {
|
|
250
|
+
return Array.isArray(value) &&
|
|
251
|
+
value.every((h) => typeof h === 'object' &&
|
|
252
|
+
h !== null &&
|
|
253
|
+
typeof h.key === 'string' &&
|
|
254
|
+
typeof h.value === 'string')
|
|
255
|
+
? null
|
|
256
|
+
: t.cfgHeaders;
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/servers-config/parse.ts"],"names":[],"mappings":"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"}
|