@knowttl/technitium-axi 0.0.0-stage → 0.1.0
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/README.md +73 -2
- package/axi-credential.schema.json +37 -0
- package/bin/technitium-axi.js +10 -0
- package/command-inventory.json +2221 -0
- package/dist/archive-contract.js +27 -0
- package/dist/catalogue.js +20 -0
- package/dist/cli.js +132 -0
- package/dist/credentials.js +149 -0
- package/dist/errors.js +14 -0
- package/dist/private-file.js +75 -0
- package/dist/read-contracts.js +50 -0
- package/dist/reads.js +330 -0
- package/dist/vendor.js +122 -0
- package/package.json +22 -4
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Entry names and selection rules reviewed against the pinned vendor handlers.
|
|
2
|
+
export const archiveContract = {
|
|
3
|
+
sourceRevision: '0dd592aad21eced4221a9a46204cb1a299972ca0',
|
|
4
|
+
sources: {
|
|
5
|
+
'DnsWebService.cs': '1e2f492baa3113d09704589bf0109fd4c9bbc05f65204918d0d485ba8ae5753e',
|
|
6
|
+
'WebServiceClusterApi.cs': '2bb0efaa14335f9bfa74844db917021a2d2869604d1531b2e85e3105cad1e6d5',
|
|
7
|
+
'Cluster/ClusterManager.cs': '787086de7f3a2b625e788042d4f8ef2aa8d6ef27c9d235be2ffc94922d4ff496',
|
|
8
|
+
},
|
|
9
|
+
backup: {
|
|
10
|
+
authConfig: ['auth.config'], clusterConfig: ['cluster.config'],
|
|
11
|
+
webServiceSettings: ['webservice.config', '<certificate-relative-path-within-config>'],
|
|
12
|
+
dnsSettings: ['dns.config', '<certificate-relative-path-within-config>'],
|
|
13
|
+
logSettings: ['log.config'], zones: ['zones/*.zone'], allowedZones: ['allowed.config'],
|
|
14
|
+
blockedZones: ['blocked.config'], blockLists: ['blocklist.config', 'blocklists/*'],
|
|
15
|
+
apps: ['apps/**/*'], scopes: ['scopes/*.scope'], stats: ['stats/*.stat', 'stats/*.dstat'], logs: ['logs/*.log'],
|
|
16
|
+
},
|
|
17
|
+
transfer: {
|
|
18
|
+
changedFiles: ['auth.config', 'dns.config', 'allowed.config', 'blocked.config', 'blocklist.config'],
|
|
19
|
+
excludedComponents: ['clusterConfig', 'webServiceSettings', 'logSettings', 'scopes', 'stats', 'logs'],
|
|
20
|
+
excludedEntries: ['zones/*.zone', 'blocklists/*', '<certificate-relative-path-within-config>'],
|
|
21
|
+
comparison: 'file mtime > If-Modified-Since; DNSSEC key StateChangedOn > If-Modified-Since',
|
|
22
|
+
keys: 'zones/<zone-name>.keys: signed Primary members of the cluster catalog; includeZones forces eligible selected names, changed unselected keys remain eligible',
|
|
23
|
+
appEntries: { unchanged: 'apps/<name>/.exists', package: 'apps/<name>/<name>.zip', config: 'apps/<name>/dnsApp.config' },
|
|
24
|
+
appMarker: 'Emit zero-byte .exists only when neither package nor config is newer; empty and key-only worlds have no app directories',
|
|
25
|
+
response: { status: 200, contentType: 'application/zip', lastModified: 'RFC 1123 GMT response time', emptyZip: 'valid protected result' },
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readContracts } from './read-contracts.js';
|
|
2
|
+
import { archiveContract } from './archive-contract.js';
|
|
3
|
+
export const readFlags = ['--json', '--help', '--config', '--profile', '--result-file', '--fields', '--full', '--max-bytes', '--max-requests', '--max-pages', '--max-elapsed-ms'];
|
|
4
|
+
export const commands = [
|
|
5
|
+
{ command: 'schema', description: 'Show the credential profile JSON Schema', effect: 'local', arguments: [], flags: ['--json', '--help'] },
|
|
6
|
+
{ command: 'inventory', description: 'Show the packaged command inventory', effect: 'local', arguments: [], flags: ['--json', '--help'] },
|
|
7
|
+
{ command: 'status', description: 'Read the selected server status without sending a credential', effect: 'read', operation: 'OP-001', arguments: [], flags: ['--json', '--help', '--config', '--profile'] },
|
|
8
|
+
{ command: 'user session get', description: 'Read the selected session with a redacted projection and optional protected result file', effect: 'read', operation: 'OP-006', arguments: [], flags: ['--json', '--help', '--config', '--profile', '--result-file'] },
|
|
9
|
+
{ command: 'auth status', description: 'Verify the selected credential and report its source without values', effect: 'local', arguments: [], flags: ['--json', '--help', '--config', '--profile'] },
|
|
10
|
+
{ command: 'profile list', description: 'List profile names from the selected config', effect: 'local', arguments: [], flags: ['--json', '--help', '--config'] },
|
|
11
|
+
{ command: 'config path', description: 'Show the selected config path', effect: 'local', arguments: [], flags: ['--json', '--help', '--config'] },
|
|
12
|
+
{ command: 'config import', description: 'Validate and atomically install a config containing SecretRefs', effect: 'local', arguments: [], flags: ['--json', '--help', '--config', '--input-file'] },
|
|
13
|
+
{ command: 'doctor', description: 'Verify credential protection and perform one bounded session read', effect: 'read', arguments: [], flags: ['--json', '--help', '--config', '--profile'] },
|
|
14
|
+
...readContracts.filter(item => !['OP-001', 'OP-006'].includes(item.id)).map(item => ({
|
|
15
|
+
command: item.command, description: `Read ${item.command}`, effect: 'read', operation: item.id,
|
|
16
|
+
arguments: item.parameters, flags: [...readFlags, ...item.parameters.map(parameter => parameter.flag)],
|
|
17
|
+
...(['OP-084', 'OP-125'].includes(item.id) ? { archive: archiveContract } : {}),
|
|
18
|
+
})),
|
|
19
|
+
];
|
|
20
|
+
export const inventory = { version: 1, tool: 'technitium-axi', commands };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { encode } from '@toon-format/toon';
|
|
6
|
+
import { commands, inventory } from './catalogue.js';
|
|
7
|
+
import { configPath, loadConfig, selectProfile } from './credentials.js';
|
|
8
|
+
import { ToolError } from './errors.js';
|
|
9
|
+
import { PrivateFiles } from './private-file.js';
|
|
10
|
+
import { Redactor, saveResult, vendorRead } from './vendor.js';
|
|
11
|
+
import { readContracts } from './read-contracts.js';
|
|
12
|
+
import { readInputs, readOperation } from './reads.js';
|
|
13
|
+
const help = {
|
|
14
|
+
description: 'Manage Technitium DNS Server through one selected credential profile', commands,
|
|
15
|
+
flags: ['--help', '--version', '-v', '-V', '--json', '--config', '--profile'],
|
|
16
|
+
examples: ['technitium-axi status --profile <name>', 'technitium-axi schema --json', 'technitium-axi user session get'],
|
|
17
|
+
};
|
|
18
|
+
export async function main(args) {
|
|
19
|
+
const redactor = new Redactor();
|
|
20
|
+
const json = args.includes('--json');
|
|
21
|
+
const print = (value) => { console.log(redactor.text(json ? JSON.stringify(value) : encode(value))); };
|
|
22
|
+
try {
|
|
23
|
+
const options = {};
|
|
24
|
+
const readOptions = {};
|
|
25
|
+
const valueFlags = new Set(['--config', '--profile', '--result-file', '--input-file', '--fields', '--max-bytes', '--max-requests', '--max-pages', '--max-elapsed-ms',
|
|
26
|
+
...readContracts.flatMap(item => item.parameters.map(parameter => parameter.flag))]);
|
|
27
|
+
const positional = [];
|
|
28
|
+
const flags = new Set();
|
|
29
|
+
for (let i = 0; i < args.length; i++) {
|
|
30
|
+
const arg = args[i];
|
|
31
|
+
if (!arg.startsWith('-')) {
|
|
32
|
+
positional.push(arg);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (flags.has(arg))
|
|
36
|
+
throw new ToolError('USAGE', 'Repeated flag', 'Provide each flag once');
|
|
37
|
+
flags.add(arg);
|
|
38
|
+
if (valueFlags.has(arg)) {
|
|
39
|
+
const value = args[++i];
|
|
40
|
+
if (value === undefined || value.startsWith('-') || (value === '' && ['--config', '--profile', '--result-file', '--input-file'].includes(arg)))
|
|
41
|
+
throw new ToolError('USAGE', 'Flag requires a value', `Use ${arg} <value>`);
|
|
42
|
+
if (arg === '--config')
|
|
43
|
+
options.config = value;
|
|
44
|
+
else if (arg === '--profile')
|
|
45
|
+
options.profile = value;
|
|
46
|
+
else if (arg === '--result-file')
|
|
47
|
+
options.resultFile = value;
|
|
48
|
+
else if (arg === '--input-file')
|
|
49
|
+
options.inputFile = value;
|
|
50
|
+
else
|
|
51
|
+
readOptions[arg] = value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const command = positional.join(' ');
|
|
55
|
+
const selected = commands.find(item => item.command === command);
|
|
56
|
+
const allowed = selected ? [...selected.flags, '--version', '-v', '-V'] : help.flags;
|
|
57
|
+
if ([...flags].some(flag => !allowed.includes(flag)))
|
|
58
|
+
throw new ToolError('USAGE', 'Unknown or prohibited flag', `Valid flags: ${allowed.join(', ')}`);
|
|
59
|
+
if (command && !selected)
|
|
60
|
+
throw new ToolError('USAGE', 'Unknown command or unexpected argument', 'Run technitium-axi --help');
|
|
61
|
+
if (args.some(arg => ['--version', '-v', '-V'].includes(arg))) {
|
|
62
|
+
console.log(JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')).version);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (flags.has('--help')) {
|
|
66
|
+
print(selected ? { command: selected.command, description: selected.description, flags: selected.flags, arguments: selected.arguments,
|
|
67
|
+
examples: [`technitium-axi ${selected.command}`, `technitium-axi ${selected.command} --json`] } : help);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (command === 'schema') {
|
|
71
|
+
print(JSON.parse(await readFile(new URL('../axi-credential.schema.json', import.meta.url), 'utf8')));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (command === 'inventory') {
|
|
75
|
+
print(inventory);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (command === 'config path') {
|
|
79
|
+
print({ config: configPath(options.config) });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (command === 'config import') {
|
|
83
|
+
if (!options.inputFile)
|
|
84
|
+
throw new ToolError('USAGE', 'Config input is required', 'Use config import --input-file <file>');
|
|
85
|
+
const config = await loadConfig(resolve(options.inputFile), true);
|
|
86
|
+
await new PrivateFiles().write(configPath(options.config), JSON.stringify(config));
|
|
87
|
+
print({ config: configPath(options.config), profiles: Object.keys(config.profiles).length });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (command === 'profile list') {
|
|
91
|
+
const config = await loadConfig(configPath(options.config), options.config !== undefined || process.env.TECHNITIUM_AXI_CONFIG !== undefined);
|
|
92
|
+
const profiles = Object.keys(config.profiles).sort().map(name => ({ name, default: name === config.defaultProfile }));
|
|
93
|
+
print({ count: profiles.length, profiles, help: ['Run technitium-axi auth status --profile <name>'] });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (options.resultFile && !isAbsolute(options.resultFile))
|
|
97
|
+
throw new ToolError('USAGE', 'Result file must be absolute', 'Use --result-file <absolute-path>');
|
|
98
|
+
const readContract = readContracts.find(item => item.command === command);
|
|
99
|
+
if (readContract)
|
|
100
|
+
readInputs(readContract, readOptions);
|
|
101
|
+
const credential = await selectProfile(options);
|
|
102
|
+
let token;
|
|
103
|
+
if (command && command !== 'status') {
|
|
104
|
+
token = await credential.token();
|
|
105
|
+
redactor.add(token);
|
|
106
|
+
}
|
|
107
|
+
if (command === 'auth status') {
|
|
108
|
+
print({ profile: credential.name, source: credential.source, origin: credential.profile.origin,
|
|
109
|
+
tokenKind: credential.profile.tokenKind, tokenSource: credential.tokenSource });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const result = readContract && !['OP-001', 'OP-006'].includes(readContract.id)
|
|
113
|
+
? await readOperation(credential.profile, command, token, readOptions, flags.has('--full'))
|
|
114
|
+
: await vendorRead(credential.profile, command === '' || command === 'status' ? 'status' : 'user session get', token);
|
|
115
|
+
if ('secret' in result && typeof result.secret === 'string')
|
|
116
|
+
redactor.add(result.secret);
|
|
117
|
+
let reference;
|
|
118
|
+
if (options.resultFile) {
|
|
119
|
+
await saveResult(options.resultFile, result.bytes);
|
|
120
|
+
reference = { path: options.resultFile, bytes: result.bytes.length, sha256: createHash('sha256').update(result.bytes).digest('hex') };
|
|
121
|
+
}
|
|
122
|
+
const executable = resolve(process.argv[1] ?? 'bin/technitium-axi.js');
|
|
123
|
+
const home = homedir();
|
|
124
|
+
print({ ...(command === '' ? { bin: executable.startsWith(`${home}/`) ? `~${executable.slice(home.length)}` : executable, description: help.description } : {}),
|
|
125
|
+
...result.projection, ...(reference ? { result: reference } : {}) });
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
const failure = error instanceof ToolError ? error : new ToolError('CONFIG_INVALID', 'Unable to read the selected configuration or protected file', 'Check config path, SecretRef, ownership and file permissions');
|
|
129
|
+
print({ error: { code: failure.code, message: failure.message }, help: [failure.hint] });
|
|
130
|
+
process.exitCode = failure.exitCode;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import { Ajv } from 'ajv';
|
|
5
|
+
import { PrivateFiles } from './private-file.js';
|
|
6
|
+
import { invalid, ToolError } from './errors.js';
|
|
7
|
+
const profileName = '^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$';
|
|
8
|
+
const files = new PrivateFiles();
|
|
9
|
+
const fields = { origin: 'ORIGIN', auth: 'AUTH', tokenKind: 'TOKEN_KIND', token: 'TOKEN', username: 'USERNAME', caFile: 'CA_FILE', tlsServerName: 'TLS_SERVER_NAME' };
|
|
10
|
+
export function configPath(explicit) {
|
|
11
|
+
const base = process.platform === 'win32' ? process.env.APPDATA ?? join(homedir(), 'AppData', 'Roaming')
|
|
12
|
+
: process.env.XDG_CONFIG_HOME && isAbsolute(process.env.XDG_CONFIG_HOME) ? process.env.XDG_CONFIG_HOME : join(homedir(), '.config');
|
|
13
|
+
return resolve(explicit ?? process.env.TECHNITIUM_AXI_CONFIG ?? join(base, 'technitium-axi', 'config.json'));
|
|
14
|
+
}
|
|
15
|
+
function statePath() {
|
|
16
|
+
const base = process.platform === 'win32' ? process.env.LOCALAPPDATA ?? join(homedir(), 'AppData', 'Local')
|
|
17
|
+
: process.env.XDG_STATE_HOME && isAbsolute(process.env.XDG_STATE_HOME) ? process.env.XDG_STATE_HOME : join(homedir(), '.local', 'state');
|
|
18
|
+
return join(base, 'technitium-axi', 'credentials.json');
|
|
19
|
+
}
|
|
20
|
+
export function readOnly() {
|
|
21
|
+
const value = process.env.TECHNITIUM_AXI_READ_ONLY?.toLowerCase();
|
|
22
|
+
if (value === undefined || ['0', 'false'].includes(value))
|
|
23
|
+
return false;
|
|
24
|
+
if (['1', 'true'].includes(value))
|
|
25
|
+
return true;
|
|
26
|
+
return invalid('TECHNITIUM_AXI_READ_ONLY must be a boolean');
|
|
27
|
+
}
|
|
28
|
+
async function validator() {
|
|
29
|
+
const schema = JSON.parse(await readFile(new URL('../axi-credential.schema.json', import.meta.url), 'utf8'));
|
|
30
|
+
const ajv = new Ajv({ strict: true });
|
|
31
|
+
ajv.addKeyword({ keyword: 'x-secret', schemaType: 'boolean' });
|
|
32
|
+
return { profile: ajv.compile(schema), config: ajv.compile({
|
|
33
|
+
type: 'object', additionalProperties: false, required: ['version', 'profiles'],
|
|
34
|
+
properties: { version: { const: 1 }, defaultProfile: { type: 'string', pattern: profileName },
|
|
35
|
+
profiles: { type: 'object', propertyNames: { pattern: profileName }, additionalProperties: schema } },
|
|
36
|
+
}) };
|
|
37
|
+
}
|
|
38
|
+
export async function loadConfig(path, explicit = false) {
|
|
39
|
+
let raw;
|
|
40
|
+
try {
|
|
41
|
+
raw = await files.read(path, false);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error.code !== 'ENOENT')
|
|
45
|
+
throw error;
|
|
46
|
+
if (explicit)
|
|
47
|
+
invalid('Selected config file does not exist');
|
|
48
|
+
return { version: 1, profiles: {} };
|
|
49
|
+
}
|
|
50
|
+
let config;
|
|
51
|
+
try {
|
|
52
|
+
config = JSON.parse(raw);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
invalid('Config must contain valid JSON');
|
|
56
|
+
}
|
|
57
|
+
if (!(await validator()).config(config))
|
|
58
|
+
invalid('Invalid config, profile fields or SecretRef');
|
|
59
|
+
for (const profile of Object.values(config.profiles)) {
|
|
60
|
+
validateOrigin(profile);
|
|
61
|
+
if (profile.caFile)
|
|
62
|
+
profile.caFile = resolve(dirname(path), profile.caFile);
|
|
63
|
+
}
|
|
64
|
+
if (config.defaultProfile && !Object.hasOwn(config.profiles, config.defaultProfile))
|
|
65
|
+
invalid('Default profile does not exist');
|
|
66
|
+
return config;
|
|
67
|
+
}
|
|
68
|
+
export async function selectProfile(options) {
|
|
69
|
+
readOnly();
|
|
70
|
+
const path = configPath(options.config);
|
|
71
|
+
const config = await loadConfig(path, options.config !== undefined || process.env.TECHNITIUM_AXI_CONFIG !== undefined);
|
|
72
|
+
const requested = options.profile ?? process.env.TECHNITIUM_AXI_PROFILE;
|
|
73
|
+
const envFields = Object.entries(fields).filter(([, field]) => process.env[`TECHNITIUM_AXI_${field}`] !== undefined);
|
|
74
|
+
let name;
|
|
75
|
+
let source;
|
|
76
|
+
let profile;
|
|
77
|
+
if (requested !== undefined) {
|
|
78
|
+
if (!new RegExp(profileName).test(requested))
|
|
79
|
+
invalid('Invalid profile name');
|
|
80
|
+
const selected = config.profiles[requested];
|
|
81
|
+
if (!selected)
|
|
82
|
+
throw new ToolError('PROFILE_NOT_FOUND', 'Selected profile does not exist', 'Run technitium-axi profile list');
|
|
83
|
+
name = requested;
|
|
84
|
+
source = options.profile !== undefined ? 'flag' : 'profile-env';
|
|
85
|
+
profile = selected;
|
|
86
|
+
}
|
|
87
|
+
else if (envFields.length) {
|
|
88
|
+
const value = Object.fromEntries(envFields.map(([field, suffix]) => [field, field === 'token'
|
|
89
|
+
? { env: 'TECHNITIUM_AXI_TOKEN' } : process.env[`TECHNITIUM_AXI_${suffix}`]]));
|
|
90
|
+
if (!(await validator()).profile(value))
|
|
91
|
+
invalid('Incomplete or invalid environment profile');
|
|
92
|
+
if (value.caFile && !isAbsolute(value.caFile))
|
|
93
|
+
invalid('Environment caFile must be an absolute path');
|
|
94
|
+
name = 'env';
|
|
95
|
+
source = 'environment';
|
|
96
|
+
profile = value;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const names = Object.keys(config.profiles);
|
|
100
|
+
const selected = config.defaultProfile ?? (names.length === 1 ? names[0] : undefined);
|
|
101
|
+
if (!selected)
|
|
102
|
+
throw new ToolError(names.length ? 'PROFILE_AMBIGUOUS' : 'PROFILE_REQUIRED', 'Select a credential profile', 'Use --config <file> --profile <name>');
|
|
103
|
+
name = selected;
|
|
104
|
+
source = config.defaultProfile ? 'default' : 'sole';
|
|
105
|
+
profile = config.profiles[name];
|
|
106
|
+
}
|
|
107
|
+
validateOrigin(profile);
|
|
108
|
+
const tokenRef = profile.token ?? { env: 'TECHNITIUM_AXI_TOKEN' };
|
|
109
|
+
const tokenSource = 'env' in tokenRef ? { env: tokenRef.env } : 'file' in tokenRef ? { file: tokenRef.file } : { stored: statePath() };
|
|
110
|
+
return { name, source, path, profile, tokenSource, async token() {
|
|
111
|
+
let token;
|
|
112
|
+
if ('env' in tokenRef)
|
|
113
|
+
token = process.env[tokenRef.env];
|
|
114
|
+
else if ('file' in tokenRef)
|
|
115
|
+
token = (await files.read(tokenRef.file)).replace(/^\uFEFF/, '').trimEnd();
|
|
116
|
+
else {
|
|
117
|
+
let stored;
|
|
118
|
+
try {
|
|
119
|
+
stored = JSON.parse(await files.read(statePath()));
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (error.code !== 'ENOENT')
|
|
123
|
+
throw error;
|
|
124
|
+
stored = {};
|
|
125
|
+
}
|
|
126
|
+
const entry = stored[name];
|
|
127
|
+
const binding = { origin: profile.origin, username: profile.username ?? null, tokenKind: profile.tokenKind,
|
|
128
|
+
tlsServerName: profile.tlsServerName ?? null, caFile: profile.caFile ?? null };
|
|
129
|
+
if (entry && JSON.stringify(entry.binding) === JSON.stringify(binding))
|
|
130
|
+
token = entry.token;
|
|
131
|
+
}
|
|
132
|
+
if (!token)
|
|
133
|
+
throw new ToolError('AUTH_REQUIRED', 'A supplied token is required', 'Set the environment variable or protected file named by the selected token SecretRef', 1);
|
|
134
|
+
if (/[\s\x00-\x1f\x7f]/.test(token))
|
|
135
|
+
invalid('Token contains invalid header characters');
|
|
136
|
+
return token;
|
|
137
|
+
} };
|
|
138
|
+
}
|
|
139
|
+
function validateOrigin(profile) {
|
|
140
|
+
let url;
|
|
141
|
+
try {
|
|
142
|
+
url = new URL(profile.origin);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
invalid('Invalid HTTPS origin');
|
|
146
|
+
}
|
|
147
|
+
if (url.origin !== profile.origin || url.protocol !== 'https:')
|
|
148
|
+
invalid('Use a canonical HTTPS origin without a path, query or userinfo');
|
|
149
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class ToolError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
hint;
|
|
4
|
+
exitCode;
|
|
5
|
+
constructor(code, message, hint, exitCode = 2) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.hint = hint;
|
|
9
|
+
this.exitCode = exitCode;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function invalid(message) {
|
|
13
|
+
throw new ToolError('CONFIG_INVALID', message, 'Run technitium-axi schema --json to inspect valid profile fields');
|
|
14
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { chmod, lstat, mkdir, open, realpath, rename, rm } from 'node:fs/promises';
|
|
3
|
+
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { ToolError } from './errors.js';
|
|
6
|
+
export class PrivateFiles {
|
|
7
|
+
platform;
|
|
8
|
+
constructor(platform = process.platform) {
|
|
9
|
+
this.platform = platform;
|
|
10
|
+
}
|
|
11
|
+
requireFileProtection() {
|
|
12
|
+
if (this.platform === 'win32')
|
|
13
|
+
throw new ToolError('CONFIG_INSECURE', 'Windows file ownership and ACL verification is PENDING', 'Use an environment-only profile with an env SecretRef and no config or result file');
|
|
14
|
+
}
|
|
15
|
+
insecure(path) {
|
|
16
|
+
throw new ToolError('CONFIG_INSECURE', 'Unsafe file ownership, permissions or redirected path', `Use an owned regular file and chmod 600 ${path}`);
|
|
17
|
+
}
|
|
18
|
+
async direct(path) {
|
|
19
|
+
if (!isAbsolute(path))
|
|
20
|
+
this.insecure(path);
|
|
21
|
+
if ((await lstat(path)).isSymbolicLink() || await realpath(path) !== resolve(path))
|
|
22
|
+
this.insecure(path);
|
|
23
|
+
}
|
|
24
|
+
async read(path, secret = true) {
|
|
25
|
+
const before = await lstat(path);
|
|
26
|
+
this.requireFileProtection();
|
|
27
|
+
await this.direct(path);
|
|
28
|
+
if (!before.isFile() || before.isSymbolicLink())
|
|
29
|
+
this.insecure(path);
|
|
30
|
+
if (before.uid !== process.getuid?.() || (before.mode & (secret ? 0o077 : 0o022)) !== 0)
|
|
31
|
+
this.insecure(path);
|
|
32
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
33
|
+
try {
|
|
34
|
+
const opened = await handle.stat();
|
|
35
|
+
if (opened.dev !== before.dev || opened.ino !== before.ino)
|
|
36
|
+
this.insecure(path);
|
|
37
|
+
if (opened.uid !== process.getuid?.() || (opened.mode & (secret ? 0o077 : 0o022)) !== 0)
|
|
38
|
+
this.insecure(path);
|
|
39
|
+
return await handle.readFile('utf8');
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await handle.close();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async write(path, bytes) {
|
|
46
|
+
this.requireFileProtection();
|
|
47
|
+
if (!isAbsolute(path))
|
|
48
|
+
this.insecure(path);
|
|
49
|
+
const directory = dirname(path);
|
|
50
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
51
|
+
await this.direct(directory);
|
|
52
|
+
if ((await lstat(directory)).uid !== process.getuid?.())
|
|
53
|
+
this.insecure(directory);
|
|
54
|
+
await chmod(directory, 0o700);
|
|
55
|
+
try {
|
|
56
|
+
await this.read(path);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (error.code !== 'ENOENT')
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
63
|
+
const handle = await open(temporary, 'wx', 0o600);
|
|
64
|
+
try {
|
|
65
|
+
await handle.writeFile(bytes);
|
|
66
|
+
await handle.sync();
|
|
67
|
+
await handle.close();
|
|
68
|
+
await rename(temporary, path);
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
await handle.close();
|
|
72
|
+
await rm(temporary, { force: true });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Contracts reviewed against Technitium DNS Server API at 0dd592aad21eced4221a9a46204cb1a299972ca0.
|
|
2
|
+
// Only documented fields are eligible for ordinary output; unknown fields stay in protected results.
|
|
3
|
+
export const readContracts = [
|
|
4
|
+
{ "id": "OP-001", "command": "status", "path": "/api/status", "kind": "json", "confidential": false, "parameters": [], "response": { "hasDefaultCredentials": "boolean", "ssoEnabled": "boolean", "server": "string", "status": "string" } },
|
|
5
|
+
{ "id": "OP-006", "command": "user session get", "path": "/api/user/session/get", "kind": "json", "confidential": true, "parameters": [], "response": { "username": "string", "info": { "version": "string", "permissions": {} } } },
|
|
6
|
+
{ "id": "OP-012", "command": "user profile get", "path": "/api/user/profile/get", "kind": "json", "confidential": false, "parameters": [], "response": { "displayName": "string", "username": "string", "isSsoUser": "boolean", "totpEnabled": "boolean", "disabled": "boolean", "previousSessionLoggedOn": "string", "previousSessionRemoteAddress": "string", "recentSessionLoggedOn": "string", "recentSessionRemoteAddress": "string", "sessionTimeoutSeconds": "number", "memberOfGroups": { "array": "string" }, "sessions": { "array": { "username": "string", "isCurrentSession": "boolean", "type": "string", "lastSeen": "string", "lastSeenRemoteAddress": "string", "lastSeenUserAgent": "string", "partialToken": "string", "tokenName": "nullable" } } } },
|
|
7
|
+
{ "id": "OP-014", "command": "user check-for-update", "path": "/api/user/checkForUpdate", "kind": "json", "confidential": false, "parameters": [], "response": { "updateAvailable": "boolean", "updateVersion": "string", "currentVersion": "string", "updateTitle": "string", "updateMessage": "string", "downloadLink": "string", "instructionsLink": "string", "changeLogLink": "string" } },
|
|
8
|
+
{ "id": "OP-015", "command": "dashboard metrics json", "path": "/api/dashboard/metrics/json", "kind": "json", "confidential": false, "parameters": [], "response": { "uptimestamp": "string", "uptimeSeconds": "number", "lifetimeCounters": { "totalQueries": "number", "totalNoError": "number", "totalServerFailure": "number", "totalNxDomain": "number", "totalRefused": "number", "totalAuthoritative": "number", "totalRecursive": "number", "totalCached": "number", "totalBlocked": "number", "totalDropped": "number", "totalClients": "number" } } },
|
|
9
|
+
{ "id": "OP-016", "command": "dashboard metrics text", "path": "/api/dashboard/metrics/text", "kind": "prometheus-text", "confidential": false, "parameters": [], "response": {} },
|
|
10
|
+
{ "id": "OP-017", "command": "dashboard stats get", "path": "/api/dashboard/stats/get", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "type", "flag": "--type", "type": "string", "required": false, "location": "form", "values": ["LastHour", "LastDay", "LastWeek", "LastMonth", "LastYear", "Custom"] }, { "name": "utc", "flag": "--utc", "type": "boolean", "required": false, "location": "form" }, { "name": "dontTrimQueryTypeData", "flag": "--dont-trim-query-type-data", "type": "boolean", "required": false, "location": "form" }, { "name": "start", "flag": "--start", "type": "date", "required": false, "location": "form" }, { "name": "end", "flag": "--end", "type": "date", "required": false, "location": "form" }], "response": { "stats": { "totalQueries": "number", "totalNoError": "number", "totalServerFailure": "number", "totalNxDomain": "number", "totalRefused": "number", "totalAuthoritative": "number", "totalRecursive": "number", "totalCached": "number", "totalBlocked": "number", "totalDropped": "number", "totalClients": "number", "zones": "number", "cachedEntries": "number", "allowedZones": "number", "blockedZones": "number", "allowListZones": "number", "blockListZones": "number" }, "mainChartData": { "labelFormat": "string", "labels": { "array": "string" }, "datasets": { "array": { "label": "string", "backgroundColor": "string", "borderColor": "string", "borderWidth": "number", "fill": "boolean", "data": { "array": "number" } } } }, "queryResponseChartData": { "labels": { "array": "string" }, "datasets": { "array": { "data": { "array": "number" }, "backgroundColor": { "array": "string" } } } }, "queryTypeChartData": { "labels": { "array": "string" }, "datasets": { "array": { "data": { "array": "number" }, "backgroundColor": { "array": "string" } } } }, "protocolTypeChartData": { "labels": { "array": "string" }, "datasets": { "array": { "data": { "array": "number" }, "backgroundColor": { "array": "string" } } } }, "topClients": { "array": { "name": "string", "domain": "string", "hits": "number", "rateLimited": "boolean" } }, "topDomains": { "array": { "name": "string", "hits": "number" } }, "topBlockedDomains": { "array": { "name": "string", "hits": "number" } } } },
|
|
11
|
+
{ "id": "OP-018", "command": "dashboard stats get-top", "path": "/api/dashboard/stats/getTop", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "type", "flag": "--type", "type": "string", "required": false, "location": "form", "values": ["LastHour", "LastDay", "LastWeek", "LastMonth", "LastYear", "custom"] }, { "name": "start", "flag": "--start", "type": "date", "required": false, "location": "form" }, { "name": "end", "flag": "--end", "type": "date", "required": false, "location": "form" }, { "name": "statsType", "flag": "--stats-type", "type": "string", "required": true, "location": "form", "values": ["TopClients", "TopDomains", "TopBlockedDomains"] }, { "name": "limit", "flag": "--limit", "type": "integer", "required": false, "location": "form" }, { "name": "noReverseLookup", "flag": "--no-reverse-lookup", "type": "boolean", "required": false, "location": "form" }, { "name": "onlyRateLimitedClients", "flag": "--only-rate-limited-clients", "type": "boolean", "required": false, "location": "form" }], "response": { "topClients": { "array": { "name": "string", "domain": "nullable", "hits": "number", "rateLimited": "boolean" } }, "topDomains": { "array": { "name": "string", "hits": "number" } }, "topBlockedDomains": { "array": { "name": "string", "hits": "number" } } } },
|
|
12
|
+
{ "id": "OP-020", "command": "zone list", "path": "/api/zones/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "filterName", "flag": "--filter-name", "type": "string", "required": false, "location": "form" }, { "name": "filterType", "flag": "--filter-type", "type": "string", "required": false, "location": "form", "values": ["Primary", "Secondary", "Stub", "Forwarder", "SecondaryForwarder", "Catalog", "SecondaryCatalog"] }, { "name": "pageNumber", "flag": "--page-number", "type": "integer", "required": false, "location": "form" }, { "name": "zonesPerPage", "flag": "--zones-per-page", "type": "integer", "required": false, "location": "form" }], "response": { "pageNumber": "number", "totalPages": "number", "totalZones": "number", "zones": { "array": { "name": "string", "type": "string", "dnssecStatus": "string", "soaSerial": "number", "expiry": "string", "isExpired": "boolean", "syncFailed": "boolean", "lastModified": "string", "disabled": "boolean", "internal": "boolean", "notifyFailed": "boolean", "notifyFailedFor": { "array": "string" } } } } },
|
|
13
|
+
{ "id": "OP-021", "command": "zone catalog list", "path": "/api/zones/catalogs/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "catalogZoneNames": { "array": "string" } } },
|
|
14
|
+
{ "id": "OP-024", "command": "zone export", "path": "/api/zones/export", "kind": "zone-text", "confidential": true, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "zone", "flag": "--zone", "type": "string", "required": true, "location": "form" }], "response": {} },
|
|
15
|
+
{ "id": "OP-031", "command": "zone options get", "path": "/api/zones/options/get", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "zone", "flag": "--zone", "type": "string", "required": true, "location": "form" }, { "name": "includeAvailableCatalogZoneNames", "flag": "--include-available-catalog-zone-names", "type": "boolean", "required": false, "location": "form" }, { "name": "includeAvailableTsigKeyNames", "flag": "--include-available-tsig-key-names", "type": "boolean", "required": false, "location": "form" }], "response": { "name": "string", "type": "string", "internal": "boolean", "dnssecStatus": "string", "notifyFailed": "boolean", "notifyFailedFor": { "array": "string" }, "disabled": "boolean", "catalog": "nullable", "overrideCatalogQueryAccess": "boolean", "overrideCatalogZoneTransfer": "boolean", "overrideCatalogNotify": "boolean", "queryAccess": "string", "queryAccessNetworkACL": { "array": "string" }, "zoneTransfer": "string", "zoneTransferNetworkACL": { "array": "string" }, "zoneTransferTsigKeyNames": { "array": "string" }, "notify": "string", "notifyNameServers": { "array": "string" }, "update": "string", "updateNetworkACL": { "array": "string" }, "updateSecurityPolicies": { "array": { "tsigKeyName": "string", "domain": "string", "allowedTypes": { "array": "string" } } }, "availableCatalogZoneNames": { "array": "string" }, "availableTsigKeyNames": { "array": "string" } } },
|
|
16
|
+
{ "id": "OP-033", "command": "zone permission get", "path": "/api/zones/permissions/get", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "zone", "flag": "--zone", "type": "string", "required": true, "location": "form" }, { "name": "includeUsersAndGroups", "flag": "--include-users-and-groups", "type": "boolean", "required": false, "location": "form" }], "response": { "section": "string", "subItem": "string", "userPermissions": { "array": { "username": "string", "canView": "boolean", "canModify": "boolean", "canDelete": "boolean" } }, "groupPermissions": { "array": { "name": "string", "canView": "boolean", "canModify": "boolean", "canDelete": "boolean" } }, "users": { "array": "string" }, "groups": { "array": "string" } } },
|
|
17
|
+
{ "id": "OP-037", "command": "zone dnssec view-ds", "path": "/api/zones/dnssec/viewDS", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "zone", "flag": "--zone", "type": "string", "required": true, "location": "form" }], "response": { "name": "string", "type": "string", "internal": "boolean", "disabled": "boolean", "dnssecStatus": "string", "dsRecords": { "array": { "keyTag": "number", "dnsKeyState": "string", "dnsKeyStateReadyBy": "string", "algorithm": "string", "publicKey": "string", "digests": { "array": { "digestType": "string", "digest": "string" } } } } } },
|
|
18
|
+
{ "id": "OP-038", "command": "zone dnssec properties get", "path": "/api/zones/dnssec/properties/get", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "zone", "flag": "--zone", "type": "string", "required": true, "location": "form" }], "response": { "name": "string", "type": "string", "internal": "boolean", "disabled": "boolean", "dnssecStatus": "string", "dnsKeyTtl": "number", "dnssecPrivateKeys": { "array": { "keyTag": "number", "keyType": "string", "algorithm": "string", "state": "string", "stateChangedOn": "string", "stateReadyBy": "string", "isRetiring": "boolean", "rolloverDays": "number" } } } },
|
|
19
|
+
{ "id": "OP-050", "command": "zone record get", "path": "/api/zones/records/get", "kind": "json", "confidential": true, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "domain", "flag": "--domain", "type": "string", "required": true, "location": "form" }, { "name": "zone", "flag": "--zone", "type": "string", "required": false, "location": "form" }, { "name": "listZone", "flag": "--list-zone", "type": "boolean", "required": false, "location": "form" }], "response": { "zone": { "name": "string", "type": "string", "internal": "boolean", "dnssecStatus": "string", "disabled": "boolean" }, "records": { "array": { "disabled": "boolean", "name": "string", "type": "string", "ttl": "number", "rData": { "ipAddress": "string", "nameServer": "string", "primaryNameServer": "string", "responsiblePerson": "string", "serial": "number", "refresh": "number", "retry": "number", "expire": "number", "minimum": "number", "typeCovered": "string", "algorithm": "string", "labels": "number", "originalTtl": "number", "signatureExpiration": "string", "signatureInception": "string", "keyTag": "number", "signersName": "string", "signature": "string", "flags": "string", "protocol": "string-or-number", "publicKey": "string", "computedKeyTag": "number", "dnsKeyState": "string", "computedDigests": { "array": { "digestType": "string", "digest": "string" } }, "dnsKeyStateReadyBy": "string", "hashAlgorithm": "string", "iterations": "number", "salt": "string", "nextHashedOwnerName": "string", "types": { "array": "string" }, "digestType": "string", "digest": "string", "cname": "string", "text": "string", "splitText": "boolean", "characterStringsBase64": { "array": "string" }, "forwarder": "string", "dnssecValidation": "boolean", "proxyType": "string", "proxyAddress": "string", "proxyPort": "number", "proxyUsername": "nullable" }, "dnssecStatus": "string", "lastUsedOn": "string" } } } },
|
|
20
|
+
{ "id": "OP-053", "command": "cache list", "path": "/api/cache/list", "kind": "json", "confidential": false, "parameters": [{ "name": "domain", "flag": "--domain", "type": "string", "required": false, "location": "form" }, { "name": "direction", "flag": "--direction", "type": "string", "required": false, "location": "form", "values": ["up", "down"] }], "response": { "domain": "string", "zones": { "array": "string" }, "records": { "array": { "name": "string", "type": "string", "ttl": "string", "rData": { "value": "string" } } } } },
|
|
21
|
+
{ "id": "OP-056", "command": "allowed list", "path": "/api/allowed/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "domain", "flag": "--domain", "type": "string", "required": false, "location": "form" }, { "name": "direction", "flag": "--direction", "type": "string", "required": false, "location": "form", "values": ["up", "down"] }], "response": { "domain": "string", "zones": { "array": "string" }, "records": { "array": { "name": "string", "type": "string", "ttl": "string", "rData": { "value": "string", "primaryNameServer": "string", "responsiblePerson": "string", "serial": "number", "refresh": "number", "retry": "number", "expire": "number", "minimum": "number" } } } } },
|
|
22
|
+
{ "id": "OP-061", "command": "allowed export", "path": "/api/allowed/export", "kind": "plain-text", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": {} },
|
|
23
|
+
{ "id": "OP-062", "command": "blocked list", "path": "/api/blocked/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "domain", "flag": "--domain", "type": "string", "required": false, "location": "form" }, { "name": "direction", "flag": "--direction", "type": "string", "required": false, "location": "form", "values": ["up", "down"] }], "response": { "domain": "string", "zones": { "array": "string" }, "records": { "array": { "name": "string", "type": "string", "ttl": "string", "rData": { "value": "string", "primaryNameServer": "string", "responsiblePerson": "string", "serial": "number", "refresh": "number", "retry": "number", "expire": "number", "minimum": "number" } } } } },
|
|
24
|
+
{ "id": "OP-067", "command": "blocked export", "path": "/api/blocked/export", "kind": "plain-text", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": {} },
|
|
25
|
+
{ "id": "OP-068", "command": "app list", "path": "/api/apps/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "apps": { "array": { "name": "string", "version": "string", "dnsApps": { "array": { "classPath": "string", "description": "string", "isAppRecordRequestHandler": "boolean", "isRequestController": "boolean", "isAuthoritativeRequestHandler": "boolean", "isRequestBlockingHandler": "boolean", "isQueryLogger": "boolean", "isPostProcessor": "boolean", "recordDataTemplate": "nullable" } } } } } },
|
|
26
|
+
{ "id": "OP-069", "command": "app list-store-apps", "path": "/api/apps/listStoreApps", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "storeApps": { "array": { "name": "string", "version": "string", "description": "string", "url": "string", "size": "string", "installed": "boolean", "installedVersion": "string", "updateAvailable": "boolean" } } } },
|
|
27
|
+
{ "id": "OP-075", "command": "app config get", "path": "/api/apps/config/get", "kind": "json", "confidential": true, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "name", "flag": "--name", "type": "string", "required": true, "location": "form" }], "response": { "config": "string" } },
|
|
28
|
+
{ "id": "OP-078", "command": "dns-client health-check", "path": "/api/dnsClient/healthCheck", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "domain", "flag": "--domain", "type": "string", "required": false, "location": "form" }, { "name": "type", "flag": "--type", "type": "string", "required": false, "location": "form" }], "response": { "server": "string", "status": "string" } },
|
|
29
|
+
{ "id": "OP-079", "command": "settings get", "path": "/api/settings/get", "kind": "json", "confidential": true, "parameters": [], "response": { "version": "string", "hasDnssecPrivateKeys": "boolean", "uptimestamp": "string", "clusterInitialized": "boolean", "dnsServerDomain": "string", "dnsServerLocalEndPoints": { "array": "string" }, "dnsServerIPv4SourceAddresses": { "array": "string" }, "dnsServerIPv6SourceAddresses": { "array": "string" }, "defaultRecordTtl": "number", "defaultNsRecordTtl": "number", "defaultSoaRecordTtl": "number", "defaultResponsiblePerson": "nullable", "useSoaSerialDateScheme": "boolean", "minSoaRefresh": "number", "minSoaRetry": "number", "zoneTransferAllowedNetworks": { "array": "string" }, "notifyAllowedNetworks": { "array": "string" }, "dnsServerEnableCheckForUpdate": "boolean", "dnsAppsEnableAutomaticUpdate": "boolean", "ipv6Mode": "string", "preferIPv6": "boolean", "enableUdpSocketPool": "boolean", "socketPoolExcludedPorts": { "array": "number" }, "udpPayloadSize": "number", "dnssecValidation": "boolean", "eDnsClientSubnet": "boolean", "eDnsClientSubnetIPv4PrefixLength": "number", "eDnsClientSubnetIPv6PrefixLength": "number", "eDnsClientSubnetIpv4Override": "nullable", "eDnsClientSubnetIpv6Override": "nullable", "qpmPrefixLimitsIPv4": { "array": { "prefix": "number", "udpLimit": "number", "tcpLimit": "number" } }, "qpmPrefixLimitsIPv6": { "array": { "prefix": "number", "udpLimit": "number", "tcpLimit": "number" } }, "qpmLimitSampleMinutes": "number", "qpmLimitUdpTruncationPercentage": "number", "qpmLimitBypassList": { "array": "string" }, "clientTimeout": "number", "tcpSendTimeout": "number", "tcpReceiveTimeout": "number", "quicIdleTimeout": "number", "quicMaxInboundStreams": "number", "listenBacklog": "number", "udpSendBufferSizeKB": "number", "udpReceiveBufferSizeKB": "number", "maxConcurrentResolutionsPerCore": "number", "webServiceLocalAddresses": { "array": "string" }, "webServiceHttpPort": "number", "webServiceEnableHttpUnixSocket": "boolean", "webServiceHttpUnixSocket": "nullable", "webServiceEnableTlsUnixSocket": "boolean", "webServiceTlsUnixSocket": "nullable", "webServiceEnableTls": "boolean", "webServiceEnableHttp3": "boolean", "webServiceHttpToTlsRedirect": "boolean", "webServiceUseSelfSignedTlsCertificate": "boolean", "webServiceTlsPort": "number", "webServiceReverseProxyAddresses": { "array": "string" }, "webServiceRealIpHeader": "string", "webServiceCspFrameAncestorsHeader": "string", "webServiceTlsCertificatePath": "nullable", "enableEDnsClientSubnetSourceAddress": "boolean", "enableDnsOverUdpProxy": "boolean", "enableDnsOverTcpProxy": "boolean", "enableDnsOverHttp": "boolean", "enableDnsOverHttpUnixSocket": "boolean", "enableDnsOverHttpsUnixSocket": "boolean", "enableDnsOverTls": "boolean", "enableDnsOverHttps": "boolean", "enableDnsOverHttp3": "boolean", "enableDnsOverQuic": "boolean", "enableDnsOverHttpHelpRedirect": "boolean", "dnsOverUdpProxyPort": "number", "dnsOverTcpProxyPort": "number", "dnsOverHttpPort": "number", "dnsOverHttpUnixSocket": "nullable", "dnsOverHttpsUnixSocket": "nullable", "dnsOverTlsPort": "number", "dnsOverHttpsPort": "number", "dnsOverQuicPort": "number", "dnsReverseProxyNetworkACL": { "array": "string" }, "dnsOverHttpRealIpHeader": "string", "dnsTlsCertificatePath": "nullable", "tsigKeys": { "array": { "keyName": "string", "algorithmName": "string" } }, "recursion": "string", "recursionNetworkACL": { "array": "string" }, "randomizeName": "boolean", "qnameMinimization": "boolean", "locallyServedDnsZones": "boolean", "resolverRetries": "number", "resolverTimeout": "number", "resolverConcurrency": "number", "resolverMaxStackCount": "number", "saveCache": "boolean", "serveStale": "boolean", "serveStaleTtl": "number", "serveStaleAnswerTtl": "number", "serveStaleResetTtl": "number", "serveStaleMaxWaitTime": "number", "cacheMaximumEntries": "number", "cacheMinimumRecordTtl": "number", "cacheMaximumRecordTtl": "number", "cacheNegativeRecordTtl": "number", "cacheFailureRecordTtl": "number", "enableCachePrefetch": "boolean", "cachePrefetchEligibility": "number", "cachePrefetchTrigger": "number", "enableBlocking": "boolean", "allowTxtBlockingReport": "boolean", "blockingBypassList": { "array": "string" }, "blockingType": "string", "blockingAnswerTtl": "number", "customBlockingAddresses": { "array": "string" }, "blockListUrls": { "array": "string" }, "blockListUpdateIntervalHours": "number", "blockListNextUpdatedOn": "string", "proxy": { "type": "string", "address": "string", "port": "number", "username": "nullable" }, "forwarders": { "array": "string" }, "forwarderProtocol": "string", "concurrentForwarding": "boolean", "forwarderRetries": "number", "forwarderTimeout": "number", "forwarderConcurrency": "number", "enableLogging": "boolean", "loggingType": "string", "ignoreResolverLogs": "boolean", "logQueries": "boolean", "noStackTrace": "boolean", "useLocalTime": "boolean", "logFolder": "string", "maxLogFileDays": "number", "enableInMemoryStats": "boolean", "maxStatFileDays": "number" } },
|
|
30
|
+
{ "id": "OP-081", "command": "settings get-tsig-key-names", "path": "/api/settings/getTsigKeyNames", "kind": "json", "confidential": false, "parameters": [], "response": { "tsigKeyNames": { "array": "string" } } },
|
|
31
|
+
{ "id": "OP-084", "command": "settings backup", "path": "/api/settings/backup", "kind": "zip", "confidential": true, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "authConfig", "flag": "--auth-config", "type": "boolean", "required": false, "location": "form" }, { "name": "clusterConfig", "flag": "--cluster-config", "type": "boolean", "required": false, "location": "form" }, { "name": "webServiceSettings", "flag": "--web-service-settings", "type": "boolean", "required": false, "location": "form" }, { "name": "dnsSettings", "flag": "--dns-settings", "type": "boolean", "required": false, "location": "form" }, { "name": "logSettings", "flag": "--log-settings", "type": "boolean", "required": false, "location": "form" }, { "name": "zones", "flag": "--zones", "type": "boolean", "required": false, "location": "form" }, { "name": "allowedZones", "flag": "--allowed-zones", "type": "boolean", "required": false, "location": "form" }, { "name": "blockedZones", "flag": "--blocked-zones", "type": "boolean", "required": false, "location": "form" }, { "name": "blockLists", "flag": "--block-lists", "type": "boolean", "required": false, "location": "form" }, { "name": "apps", "flag": "--apps", "type": "boolean", "required": false, "location": "form" }, { "name": "scopes", "flag": "--scopes", "type": "boolean", "required": false, "location": "form" }, { "name": "stats", "flag": "--stats", "type": "boolean", "required": false, "location": "form" }, { "name": "logs", "flag": "--logs", "type": "boolean", "required": false, "location": "form" }], "response": {} },
|
|
32
|
+
{ "id": "OP-086", "command": "dhcp lease list", "path": "/api/dhcp/leases/list", "kind": "json", "confidential": false, "parameters": [], "response": { "leases": { "array": { "scope": "string", "type": "string", "hardwareAddress": "string", "clientIdentifier": "string", "address": "string", "hostName": "nullable", "leaseObtained": "string", "leaseExpires": "string" } } } },
|
|
33
|
+
{ "id": "OP-090", "command": "dhcp scope list", "path": "/api/dhcp/scopes/list", "kind": "json", "confidential": false, "parameters": [], "response": { "scopes": { "array": { "name": "string", "enabled": "boolean", "startingAddress": "string", "endingAddress": "string", "subnetMask": "string", "networkAddress": "string", "broadcastAddress": "string" } } } },
|
|
34
|
+
{ "id": "OP-091", "command": "dhcp scope get", "path": "/api/dhcp/scopes/get", "kind": "json", "confidential": false, "parameters": [{ "name": "name", "flag": "--name", "type": "string", "required": true, "location": "form" }], "response": { "name": "string", "startingAddress": "string", "endingAddress": "string", "subnetMask": "string", "leaseTimeDays": "number", "leaseTimeHours": "number", "leaseTimeMinutes": "number", "offerDelayTime": "number", "pingCheckEnabled": "boolean", "pingCheckTimeout": "number", "pingCheckRetries": "number", "domainName": "string", "domainSearchList": { "array": "string" }, "dnsUpdates": "boolean", "dnsOverwriteForDynamicLease": "boolean", "dnsTtl": "number", "serverAddress": "string", "serverHostName": "string", "bootFileName": "string", "routerAddress": "string", "useThisDnsServer": "boolean", "dnsServers": { "array": "string" }, "winsServers": { "array": "string" }, "ntpServers": { "array": "string" }, "staticRoutes": { "array": { "destination": "string", "subnetMask": "string", "router": "string" } }, "vendorInfo": { "array": { "identifier": "string", "information": "string" } }, "capwapAcIpAddresses": { "array": "string" }, "tftpServerAddresses": { "array": "string" }, "genericOptions": { "array": { "code": "number", "value": "string" } }, "exclusions": { "array": { "startingAddress": "string", "endingAddress": "string" } }, "reservedLeases": { "array": { "hostName": "nullable", "hardwareAddress": "string", "address": "string", "comments": "string" } }, "allowOnlyReservedLeases": "boolean", "blockLocallyAdministeredMacAddresses": "boolean", "ignoreClientIdentifierOption": "boolean" } },
|
|
35
|
+
{ "id": "OP-098", "command": "admin session list", "path": "/api/admin/sessions/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "sessions": { "array": { "username": "string", "isCurrentSession": "boolean", "type": "string", "lastSeen": "string", "lastSeenRemoteAddress": "string", "lastSeenUserAgent": "string", "partialToken": "string", "tokenName": "nullable" } } } },
|
|
36
|
+
{ "id": "OP-101", "command": "admin user list", "path": "/api/admin/users/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "users": { "array": { "displayName": "string", "username": "string", "isSsoUser": "boolean", "disabled": "boolean", "previousSessionLoggedOn": "string", "previousSessionRemoteAddress": "string", "recentSessionLoggedOn": "string", "recentSessionRemoteAddress": "string" } } } },
|
|
37
|
+
{ "id": "OP-103", "command": "admin user get", "path": "/api/admin/users/get", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "user", "flag": "--user", "type": "string", "required": true, "location": "form" }, { "name": "includeGroups", "flag": "--include-groups", "type": "boolean", "required": false, "location": "form" }], "response": { "displayName": "string", "username": "string", "isSsoUser": "boolean", "totpEnabled": "boolean", "disabled": "boolean", "previousSessionLoggedOn": "string", "previousSessionRemoteAddress": "string", "recentSessionLoggedOn": "string", "recentSessionRemoteAddress": "string", "sessionTimeoutSeconds": "number", "memberOfGroups": { "array": "string" }, "sessions": { "array": { "username": "string", "isCurrentSession": "boolean", "type": "string", "lastSeen": "string", "lastSeenRemoteAddress": "string", "lastSeenUserAgent": "string", "partialToken": "string", "tokenName": "nullable" } }, "groups": { "array": "string" } } },
|
|
38
|
+
{ "id": "OP-106", "command": "admin group list", "path": "/api/admin/groups/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "groups": { "array": { "name": "string", "description": "string" } } } },
|
|
39
|
+
{ "id": "OP-108", "command": "admin group get", "path": "/api/admin/groups/get", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "group", "flag": "--group", "type": "string", "required": true, "location": "form" }, { "name": "includeUsers", "flag": "--include-users", "type": "boolean", "required": false, "location": "form" }], "response": { "name": "string", "description": "string", "members": { "array": "string" }, "users": { "array": "string" } } },
|
|
40
|
+
{ "id": "OP-111", "command": "admin permission list", "path": "/api/admin/permissions/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "permissions": { "array": { "section": "string", "userPermissions": { "array": { "username": "string", "canView": "boolean", "canModify": "boolean", "canDelete": "boolean" } }, "groupPermissions": { "array": { "name": "string", "canView": "boolean", "canModify": "boolean", "canDelete": "boolean" } } } } } },
|
|
41
|
+
{ "id": "OP-112", "command": "admin permission get", "path": "/api/admin/permissions/get", "kind": "json", "confidential": false, "parameters": [{ "name": "section", "flag": "--section", "type": "string", "required": true, "location": "form" }, { "name": "includeUsersAndGroups", "flag": "--include-users-and-groups", "type": "boolean", "required": false, "location": "form" }], "response": { "section": "string", "userPermissions": { "array": { "username": "string", "canView": "boolean", "canModify": "boolean", "canDelete": "boolean" } }, "groupPermissions": { "array": { "name": "string", "canView": "boolean", "canModify": "boolean", "canDelete": "boolean" } }, "users": { "array": "string" }, "groups": { "array": "string" } } },
|
|
42
|
+
{ "id": "OP-114", "command": "admin sso get", "path": "/api/admin/sso/get", "kind": "json", "confidential": false, "parameters": [{ "name": "includeGroups", "flag": "--include-groups", "type": "boolean", "required": false, "location": "form" }], "response": { "ssoEnabled": "boolean", "ssoAuthority": "nullable", "ssoClientId": "nullable", "ssoMetadataAddress": "nullable", "ssoAllowSignup": "boolean", "ssoAllowSignupOnlyForMappedUsers": "boolean", "ssoGroupMap": { "array": { "remoteGroup": "string", "localGroup": "string" } }, "localGroups": { "array": "string" }, "ssoScopes": { "array": "string" } } },
|
|
43
|
+
{ "id": "OP-118", "command": "admin cluster state", "path": "/api/admin/cluster/state", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "includeServerIpAddresses", "flag": "--include-server-ip-addresses", "type": "boolean", "required": false, "location": "form" }], "response": { "clusterInitialized": "boolean", "dnsServerDomain": "string", "version": "string", "clusterDomain": "string", "heartbeatRefreshIntervalSeconds": "number", "heartbeatRetryIntervalSeconds": "number", "configRefreshIntervalSeconds": "number", "configRetryIntervalSeconds": "number", "configLastSynced": "string", "clusterNodes": { "array": { "id": "number", "name": "string", "url": "string", "type": "string", "state": "string", "version": "string", "lastSeen": "string", "ipAddress": "string" } }, "serverIpAddresses": { "array": "string" } } },
|
|
44
|
+
{ "id": "OP-125", "command": "admin cluster primary transfer-config", "path": "/api/admin/cluster/primary/transferConfig", "kind": "zip", "confidential": true, "parameters": [{ "name": "includeZones", "flag": "--include-zones", "type": "zone-list", "required": false, "location": "form" }, { "name": "If-Modified-Since", "flag": "--if-modified-since", "type": "http-date", "required": false, "location": "header" }], "response": {} },
|
|
45
|
+
{ "id": "OP-134", "command": "log list", "path": "/api/logs/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "logFiles": { "array": { "fileName": "string", "size": "string" } } } },
|
|
46
|
+
{ "id": "OP-135", "command": "log download", "path": "/api/logs/download", "kind": "log-text", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "fileName", "flag": "--file-name", "type": "string", "required": true, "location": "form" }, { "name": "limit", "flag": "--limit", "type": "integer", "required": false, "location": "form" }], "response": {} },
|
|
47
|
+
{ "id": "OP-138", "command": "log query", "path": "/api/logs/query", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "name", "flag": "--name", "type": "string", "required": true, "location": "form" }, { "name": "classPath", "flag": "--class-path", "type": "string", "required": true, "location": "form" }, { "name": "pageNumber", "flag": "--page-number", "type": "integer", "required": false, "location": "form" }, { "name": "entriesPerPage", "flag": "--entries-per-page", "type": "integer", "required": false, "location": "form" }, { "name": "descendingOrder", "flag": "--descending-order", "type": "boolean", "required": false, "location": "form" }, { "name": "start", "flag": "--start", "type": "date", "required": false, "location": "form" }, { "name": "end", "flag": "--end", "type": "date", "required": false, "location": "form" }, { "name": "clientIpAddress", "flag": "--client-ip-address", "type": "string", "required": false, "location": "form" }, { "name": "protocol", "flag": "--protocol", "type": "string", "required": false, "location": "form", "values": ["Udp", "Tcp", "Tls", "Https", "Quic"] }, { "name": "responseType", "flag": "--response-type", "type": "string", "required": false, "location": "form", "values": ["Authoritative", "Recursive", "Cached", "Blocked", "UpstreamBlocked", "CacheBlocked"] }, { "name": "rcode", "flag": "--rcode", "type": "string", "required": false, "location": "form" }, { "name": "qname", "flag": "--qname", "type": "string", "required": false, "location": "form" }, { "name": "qtype", "flag": "--qtype", "type": "string", "required": false, "location": "form" }, { "name": "qclass", "flag": "--qclass", "type": "string", "required": false, "location": "form" }], "response": { "pageNumber": "number", "totalPages": "number", "totalEntries": "number", "entries": { "array": { "rowNumber": "number", "timestamp": "string", "clientIpAddress": "string", "protocol": "string", "responseType": "string", "responseRtt": "number", "rcode": "string", "qname": "string", "qtype": "string", "qclass": "string", "answer": "string" } } } },
|
|
48
|
+
{ "id": "OP-139", "command": "log export", "path": "/api/logs/export", "kind": "csv", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "name", "flag": "--name", "type": "string", "required": true, "location": "form" }, { "name": "classPath", "flag": "--class-path", "type": "string", "required": true, "location": "form" }, { "name": "start", "flag": "--start", "type": "date", "required": false, "location": "form" }, { "name": "end", "flag": "--end", "type": "date", "required": false, "location": "form" }, { "name": "clientIpAddress", "flag": "--client-ip-address", "type": "string", "required": false, "location": "form" }, { "name": "protocol", "flag": "--protocol", "type": "string", "required": false, "location": "form", "values": ["Udp", "Tcp", "Tls", "Https", "Quic"] }, { "name": "responseType", "flag": "--response-type", "type": "string", "required": false, "location": "form", "values": ["Authoritative", "Recursive", "Cached", "Blocked", "UpstreamBlocked", "CacheBlocked"] }, { "name": "rcode", "flag": "--rcode", "type": "string", "required": false, "location": "form" }, { "name": "qname", "flag": "--qname", "type": "string", "required": false, "location": "form" }, { "name": "qtype", "flag": "--qtype", "type": "string", "required": false, "location": "form" }, { "name": "qclass", "flag": "--qclass", "type": "string", "required": false, "location": "form" }], "response": {} },
|
|
49
|
+
{ "id": "OP-141", "command": "admin ldap get", "path": "/api/admin/ldap/get", "kind": "json", "confidential": false, "parameters": [{ "name": "includeGroups", "flag": "--include-groups", "type": "boolean", "required": false, "location": "form" }], "response": { "ldapEnabled": "boolean", "ldapServer": "nullable", "ldapPort": "number", "ldapSslOption": "string", "ldapIgnoreSslErrors": "boolean", "ldapBindUsername": "nullable", "ldapSearchBase": "nullable", "ldapUserSearchFilter": "nullable", "ldapGroupAttribute": "nullable", "ldapAllowSignup": "boolean", "ldapAllowSignupOnlyForMappedUsers": "boolean", "ldapGroupMap": { "array": { "remoteGroup": "string", "localGroup": "string" } }, "localGroups": { "array": "string" } } }
|
|
50
|
+
];
|