@harperfast/harper-pro 5.0.0-alpha.2 → 5.0.0-alpha.3
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/core/CONTRIBUTING.md +2 -0
- package/core/package.json +2 -2
- package/core/resources/DatabaseTransaction.ts +1 -1
- package/core/resources/LMDBTransaction.ts +9 -4
- package/core/resources/databases.ts +1 -1
- package/core/unitTests/resources/permissions.test.js +7 -2
- package/core/unitTests/resources/txn-tracking.test.js +10 -4
- package/core/unitTests/resources/vectorIndex.test.js +1 -0
- package/dist/bin/harper.js +1 -1
- package/dist/bin/harper.js.map +1 -1
- package/dist/core/resources/DatabaseTransaction.js +1 -1
- package/dist/core/resources/DatabaseTransaction.js.map +1 -1
- package/dist/core/resources/LMDBTransaction.js +9 -5
- package/dist/core/resources/LMDBTransaction.js.map +1 -1
- package/dist/core/resources/databases.js +1 -1
- package/dist/core/resources/databases.js.map +1 -1
- package/dist/licensing/usageLicensing.js +246 -0
- package/dist/licensing/usageLicensing.js.map +1 -0
- package/dist/licensing/validation.js +149 -0
- package/dist/licensing/validation.js.map +1 -0
- package/dist/replication/replicator.js +5 -2
- package/dist/replication/replicator.js.map +1 -1
- package/dist/replication/setNode.js +0 -1
- package/dist/replication/setNode.js.map +1 -1
- package/dist/security/certificate.js +206 -6
- package/dist/security/certificate.js.map +1 -1
- package/dist/security/keyService.js +58 -0
- package/dist/security/keyService.js.map +1 -0
- package/dist/security/sshKeyOperations.js +343 -0
- package/dist/security/sshKeyOperations.js.map +1 -0
- package/licensing/usageLicensing.ts +262 -0
- package/licensing/validation.ts +191 -0
- package/npm-shrinkwrap.json +253 -253
- package/package.json +3 -2
- package/replication/replicator.ts +6 -2
- package/replication/setNode.ts +0 -1
- package/security/certificate.ts +259 -7
- package/security/keyService.ts +74 -0
- package/security/sshKeyOperations.ts +405 -0
- package/static/defaultConfig.yaml +2 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import Joi from 'joi';
|
|
2
|
+
import { join, dirname, basename } from 'node:path';
|
|
3
|
+
import { constants, access, readFile, writeFile, unlink, chmod, appendFile, mkdir, readdir } from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
import { validateBySchema } from '../core/validation/validationWrapper.js';
|
|
6
|
+
import harperLogger from '../core/utility/logging/harper_logger.js';
|
|
7
|
+
import { ClientError } from '../core/utility/errors/hdbError.js';
|
|
8
|
+
import { CONFIG_PARAMS } from '../core/utility/hdbTerms.ts';
|
|
9
|
+
import env from '../core/utility/environment/environmentManager.js';
|
|
10
|
+
import { replicateOperation } from '../replication/replicator.ts';
|
|
11
|
+
|
|
12
|
+
// SSH key name can only be alphanumeric, dash and underscores
|
|
13
|
+
const SSH_KEY_NAME_REGEX = /^[a-zA-Z0-9-_]+$/;
|
|
14
|
+
const SSH_KEY_NAME_ERROR_MSG = 'SSH key name can only contain alphanumeric, dash and underscore characters';
|
|
15
|
+
|
|
16
|
+
// Helper function to check if a file or directory exists
|
|
17
|
+
const exists = async (path: string): Promise<boolean> =>
|
|
18
|
+
access(path, constants.F_OK)
|
|
19
|
+
.then(() => true)
|
|
20
|
+
.catch(() => false);
|
|
21
|
+
|
|
22
|
+
// Helper function to write a file ensuring the directory exists
|
|
23
|
+
async function writeFileEnsureDir(filePath: string, data: string) {
|
|
24
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
25
|
+
await writeFile(filePath, data);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const addValidationSchema = Joi.object({
|
|
29
|
+
name: Joi.string().pattern(SSH_KEY_NAME_REGEX).required().messages({ 'string.pattern.base': SSH_KEY_NAME_ERROR_MSG }),
|
|
30
|
+
key: Joi.string().required(),
|
|
31
|
+
host: Joi.string().required(),
|
|
32
|
+
hostname: Joi.string().required(),
|
|
33
|
+
known_hosts: Joi.string().optional(),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const getSSHKeyValidationSchema = Joi.object({
|
|
37
|
+
name: Joi.string().required(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const updateSSHKeyValidationSchema = Joi.object({
|
|
41
|
+
name: Joi.string().required(),
|
|
42
|
+
key: Joi.string().required(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const deleteSSHKeyValidationSchema = Joi.object({
|
|
46
|
+
name: Joi.string().required(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const setSSHKnownHostsValidationSchema = Joi.object({
|
|
50
|
+
known_hosts: Joi.string().required(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
function getSSHPaths(keyName: string | undefined): {
|
|
54
|
+
sshDir: string;
|
|
55
|
+
filePath: string | undefined;
|
|
56
|
+
configFile: string;
|
|
57
|
+
knownHostsFile: string;
|
|
58
|
+
} {
|
|
59
|
+
const rootDir = env.get(CONFIG_PARAMS.ROOTPATH);
|
|
60
|
+
const sshDir = join(rootDir, 'ssh');
|
|
61
|
+
const filePath = keyName ? join(sshDir, keyName + '.key') : undefined;
|
|
62
|
+
const configFile = join(sshDir, 'config');
|
|
63
|
+
const knownHostsFile = join(sshDir, 'known_hosts');
|
|
64
|
+
|
|
65
|
+
return { sshDir, filePath, configFile, knownHostsFile };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface AddSSHKeyRequest {
|
|
69
|
+
name: string;
|
|
70
|
+
key: string;
|
|
71
|
+
host: string;
|
|
72
|
+
hostname: string;
|
|
73
|
+
known_hosts?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Adds a new SSH key along with its associated SSH config block and optional
|
|
78
|
+
* known_hosts entries. If the hostname is `github.com`, GitHub's public SSH
|
|
79
|
+
* keys are automatically fetched and added to the known_hosts file.
|
|
80
|
+
*
|
|
81
|
+
* @param req - The request object containing the SSH key details.
|
|
82
|
+
* @param req.name - The name of the SSH key to add.
|
|
83
|
+
* @param req.key - The SSH key contents to write to disk.
|
|
84
|
+
* @param req.host - The Host alias to use in the SSH config block.
|
|
85
|
+
* @param req.hostname - The HostName (real hostname) to use in the SSH config block.
|
|
86
|
+
* @param req.known_hosts - Optional known_hosts entries to append to the known_hosts file.
|
|
87
|
+
* @returns An object containing a success message and optional replication results.
|
|
88
|
+
*/
|
|
89
|
+
async function addSSHKey(req: AddSSHKeyRequest): Promise<{ message: string; replicated?: unknown[] }> {
|
|
90
|
+
const validation = validateBySchema(req, addValidationSchema);
|
|
91
|
+
if (validation) throw new ClientError(validation.message);
|
|
92
|
+
|
|
93
|
+
const { name, key, host, hostname, known_hosts } = req;
|
|
94
|
+
harperLogger?.trace('adding ssh key', name);
|
|
95
|
+
|
|
96
|
+
const { filePath, configFile, knownHostsFile } = getSSHPaths(name);
|
|
97
|
+
|
|
98
|
+
// Check if the key already exists
|
|
99
|
+
if (await exists(filePath)) {
|
|
100
|
+
throw new ClientError('Key already exists. Use update_ssh_key or delete_ssh_key and then add_ssh_key');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Create the key file
|
|
104
|
+
await writeFileEnsureDir(filePath, key);
|
|
105
|
+
await chmod(filePath, 0o600);
|
|
106
|
+
|
|
107
|
+
// Build the config block string
|
|
108
|
+
const configBlock = `#${name}
|
|
109
|
+
Host ${host}
|
|
110
|
+
HostName ${hostname}
|
|
111
|
+
User git
|
|
112
|
+
IdentityFile ${filePath}
|
|
113
|
+
IdentitiesOnly yes`;
|
|
114
|
+
|
|
115
|
+
// If the file already exists, add a new config block, otherwise write the file for the first time
|
|
116
|
+
if (await exists(configFile)) {
|
|
117
|
+
await appendFile(configFile, '\n' + configBlock);
|
|
118
|
+
} else {
|
|
119
|
+
await writeFileEnsureDir(configFile, configBlock);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let additionalMessage = '';
|
|
123
|
+
|
|
124
|
+
// Create the known_hosts file and set permissions if missing
|
|
125
|
+
if (!(await exists(knownHostsFile))) {
|
|
126
|
+
await writeFileEnsureDir(knownHostsFile, '');
|
|
127
|
+
await chmod(knownHostsFile, 0o600);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// If adding a github.com ssh key download it automatically
|
|
131
|
+
if (hostname === 'github.com') {
|
|
132
|
+
const fileContents: string = await readFile(knownHostsFile, 'utf8');
|
|
133
|
+
|
|
134
|
+
// Check if there's already github.com entries
|
|
135
|
+
if (!fileContents.includes('github.com')) {
|
|
136
|
+
try {
|
|
137
|
+
const response = await fetch('https://api.github.com/meta');
|
|
138
|
+
const respJson = await response.json();
|
|
139
|
+
const sshKeys = respJson['ssh_keys'];
|
|
140
|
+
for (const knownHost of sshKeys) {
|
|
141
|
+
await appendFile(knownHostsFile, 'github.com ' + knownHost + '\n');
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
additionalMessage =
|
|
145
|
+
'. Unable to get known hosts from github.com. Set your known hosts manually using set_ssh_known_hosts.';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (known_hosts) {
|
|
151
|
+
await appendFile(knownHostsFile, known_hosts);
|
|
152
|
+
}
|
|
153
|
+
let response = await replicateOperation(req);
|
|
154
|
+
response.message = `Added ssh key: ${name}${additionalMessage}`;
|
|
155
|
+
|
|
156
|
+
return response;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Retrieves an SSH key by name, along with any associated Host and HostName
|
|
161
|
+
* configuration from the SSH config file.
|
|
162
|
+
*
|
|
163
|
+
* @param req - The request object containing the key name.
|
|
164
|
+
* @param req.name - The name of the SSH key to retrieve.
|
|
165
|
+
* @returns An object containing the key name, the key contents, and optionally
|
|
166
|
+
* the Host and HostName from the SSH config file.
|
|
167
|
+
*/
|
|
168
|
+
async function getSSHKey(req: {
|
|
169
|
+
name: string;
|
|
170
|
+
}): Promise<{ name: string; key: string; host?: string; hostname?: string }> {
|
|
171
|
+
const validation = validateBySchema(req, getSSHKeyValidationSchema);
|
|
172
|
+
if (validation) throw new ClientError(validation.message);
|
|
173
|
+
|
|
174
|
+
const { name } = req;
|
|
175
|
+
const { filePath, configFile } = getSSHPaths(name);
|
|
176
|
+
|
|
177
|
+
if (!(await exists(filePath))) {
|
|
178
|
+
throw new ClientError(`SSH key '${name}' does not exist.`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
harperLogger?.trace(`getting ssh key`, name, filePath);
|
|
182
|
+
|
|
183
|
+
const key = await readFile(filePath, 'utf8');
|
|
184
|
+
const result: { name: string; key: string; host?: string; hostname?: string } = { name, key };
|
|
185
|
+
|
|
186
|
+
if (await exists(configFile)) {
|
|
187
|
+
const configContents = await readFile(configFile, 'utf8');
|
|
188
|
+
const { host, hostname } = extractMatchingHostAndHostname(configContents, name);
|
|
189
|
+
if (host) result.host = host;
|
|
190
|
+
if (hostname) result.hostname = hostname;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Updates an existing SSH key by overwriting the key file with new contents.
|
|
198
|
+
*
|
|
199
|
+
* @param req - The request object containing the updated key details.
|
|
200
|
+
* @param req.name - The name of the SSH key to update.
|
|
201
|
+
* @param req.key - The new SSH key contents to write to disk.
|
|
202
|
+
* @returns An object containing a success message and optional replication results.
|
|
203
|
+
*/
|
|
204
|
+
async function updateSSHKey(req: { name: string; key: string }): Promise<{ message: string; replicated?: unknown[] }> {
|
|
205
|
+
const validation = validateBySchema(req, updateSSHKeyValidationSchema);
|
|
206
|
+
if (validation) throw new ClientError(validation.message);
|
|
207
|
+
|
|
208
|
+
const { name, key } = req;
|
|
209
|
+
harperLogger?.trace(`updating ssh key`, name);
|
|
210
|
+
|
|
211
|
+
const { filePath } = getSSHPaths(name);
|
|
212
|
+
if (!(await exists(filePath))) {
|
|
213
|
+
throw new ClientError(`SSH key '${name}' does not exist. Use add_ssh_key to create it.`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await writeFileEnsureDir(filePath, key);
|
|
217
|
+
await chmod(filePath, 0o600);
|
|
218
|
+
|
|
219
|
+
const response = await replicateOperation(req);
|
|
220
|
+
response.message = `Updated ssh key: ${name}`;
|
|
221
|
+
return response;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Deletes an existing SSH key and removes its associated config block from
|
|
226
|
+
* the SSH config file.
|
|
227
|
+
*
|
|
228
|
+
* @param req - The request object containing the key name.
|
|
229
|
+
* @param req.name - The name of the SSH key to delete.
|
|
230
|
+
* @returns An object containing a success message and optional replication results.
|
|
231
|
+
*/
|
|
232
|
+
async function deleteSSHKey(req: { name: string }): Promise<{ message: string; replicated?: unknown[] }> {
|
|
233
|
+
const validation = validateBySchema(req, deleteSSHKeyValidationSchema);
|
|
234
|
+
if (validation) throw new ClientError(validation.message);
|
|
235
|
+
|
|
236
|
+
const { name } = req;
|
|
237
|
+
harperLogger?.trace(`deleting ssh key`, name);
|
|
238
|
+
|
|
239
|
+
const { filePath, configFile } = getSSHPaths(name);
|
|
240
|
+
if (!(await exists(filePath))) {
|
|
241
|
+
throw new ClientError(`SSH key '${name}' does not exist.`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (await exists(configFile)) {
|
|
245
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
246
|
+
const configBlockRegex = new RegExp(`#${escapedName}[\\S\\s]*?IdentitiesOnly yes`, 'g');
|
|
247
|
+
const fileContents = (await readFile(configFile, 'utf8')).replace(configBlockRegex, '').trim();
|
|
248
|
+
await writeFileEnsureDir(configFile, fileContents);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
await unlink(filePath);
|
|
252
|
+
|
|
253
|
+
const response = await replicateOperation(req);
|
|
254
|
+
response.message = `Deleted ssh key: ${name}`;
|
|
255
|
+
return response;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Lists all SSH keys along with their associated Host and HostName
|
|
260
|
+
* configuration from the SSH config file.
|
|
261
|
+
*
|
|
262
|
+
* @returns An array of objects containing the key name and optionally
|
|
263
|
+
* the Host and HostName from the SSH config file.
|
|
264
|
+
*/
|
|
265
|
+
async function listSSHKeys(): Promise<{ name: string; host?: string; hostname?: string }[]> {
|
|
266
|
+
const { sshDir, configFile } = getSSHPaths(undefined);
|
|
267
|
+
if (!(await exists(sshDir))) return [];
|
|
268
|
+
|
|
269
|
+
const EXCLUDED_FILES = new Set(['known_hosts', 'config']);
|
|
270
|
+
const configContents: string | null = (await exists(configFile)) ? await readFile(configFile, 'utf8') : null;
|
|
271
|
+
const files: string[] = await readdir(sshDir);
|
|
272
|
+
return files
|
|
273
|
+
.filter((file) => !EXCLUDED_FILES.has(file))
|
|
274
|
+
.map((file) => {
|
|
275
|
+
const name: string = basename(file, '.key');
|
|
276
|
+
const result: { name: string; host?: string; hostname?: string } = { name };
|
|
277
|
+
|
|
278
|
+
if (configContents) {
|
|
279
|
+
const { host, hostname } = extractMatchingHostAndHostname(configContents, name);
|
|
280
|
+
if (host) result.host = host;
|
|
281
|
+
if (hostname) result.hostname = hostname;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return result;
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Extracts the Host and HostName values from an SSH config block matching
|
|
290
|
+
* the given key name. Config blocks are identified by a leading comment
|
|
291
|
+
* in the format `#keyName`.
|
|
292
|
+
*
|
|
293
|
+
* @param configContents - The full contents of the SSH config file.
|
|
294
|
+
* @param name - The name of the SSH key whose config block to extract from.
|
|
295
|
+
* @returns An object containing the optional Host and HostName values from
|
|
296
|
+
* the matching config block, or an empty object if no match is found.
|
|
297
|
+
*/
|
|
298
|
+
function extractMatchingHostAndHostname(configContents: string, name: string): { host?: string; hostname?: string } {
|
|
299
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
300
|
+
const configBlockRegex = new RegExp(`#${escapedName}[\\S\\s]*?IdentitiesOnly yes`, 'g');
|
|
301
|
+
const match = configContents.match(configBlockRegex);
|
|
302
|
+
|
|
303
|
+
if (!match?.[0]) return {};
|
|
304
|
+
|
|
305
|
+
const configBlock = match[0];
|
|
306
|
+
|
|
307
|
+
const host = configBlock.match(/^Host\s+(.+)$/m)?.[1]?.trim();
|
|
308
|
+
const hostname = configBlock.match(/^\s*HostName\s+(.+)$/m)?.[1]?.trim();
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
...(host && { host }),
|
|
312
|
+
...(hostname && { hostname }),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Overwrites the SSH known_hosts file with the provided entries.
|
|
318
|
+
*
|
|
319
|
+
* @param req - The request object containing the known_hosts entries.
|
|
320
|
+
* @param req.known_hosts - The known_hosts entries to write to the file.
|
|
321
|
+
* @returns An object containing a success message and optional replication results.
|
|
322
|
+
*/
|
|
323
|
+
async function setSSHKnownHosts(req: { known_hosts: string }): Promise<{ message: string; replicated?: unknown[] }> {
|
|
324
|
+
const validation = validateBySchema(req, setSSHKnownHostsValidationSchema);
|
|
325
|
+
if (validation) throw new ClientError(validation.message);
|
|
326
|
+
|
|
327
|
+
const { known_hosts } = req;
|
|
328
|
+
harperLogger?.trace(`setting ssh known hosts`);
|
|
329
|
+
|
|
330
|
+
const { knownHostsFile } = getSSHPaths(undefined);
|
|
331
|
+
await writeFileEnsureDir(knownHostsFile, known_hosts);
|
|
332
|
+
await chmod(knownHostsFile, 0o600);
|
|
333
|
+
|
|
334
|
+
const response = await replicateOperation(req);
|
|
335
|
+
response.message = `Known hosts successfully set`;
|
|
336
|
+
|
|
337
|
+
return response;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Retrieves the contents of the SSH known_hosts file.
|
|
342
|
+
*
|
|
343
|
+
* @returns An object containing the known_hosts file contents,
|
|
344
|
+
* or `null` if the file does not exist.
|
|
345
|
+
*/
|
|
346
|
+
async function getSSHKnownHosts(): Promise<{ known_hosts: string | null }> {
|
|
347
|
+
harperLogger?.trace(`getting ssh known hosts`);
|
|
348
|
+
const { knownHostsFile } = getSSHPaths(undefined);
|
|
349
|
+
if (!(await exists(knownHostsFile))) {
|
|
350
|
+
return { known_hosts: null };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return { known_hosts: await readFile(knownHostsFile, 'utf8') };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// These will register the operations for the operations API. For now the method and schema are ignored,
|
|
357
|
+
// they are there for when build the REST interface for operations API
|
|
358
|
+
server.registerOperation?.({
|
|
359
|
+
name: 'add_ssh_key',
|
|
360
|
+
execute: addSSHKey,
|
|
361
|
+
httpMethod: 'PUT',
|
|
362
|
+
parametersSchema: [{ name: 'hostname', in: 'path', schema: { type: 'string' } }],
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
server.registerOperation?.({
|
|
366
|
+
name: 'get_ssh_key',
|
|
367
|
+
execute: getSSHKey,
|
|
368
|
+
httpMethod: 'GET',
|
|
369
|
+
parametersSchema: [{ name: 'hostname', in: 'path', schema: { type: 'string' } }],
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
server.registerOperation?.({
|
|
373
|
+
name: 'update_ssh_key',
|
|
374
|
+
execute: updateSSHKey,
|
|
375
|
+
httpMethod: 'PATCH',
|
|
376
|
+
parametersSchema: [{ name: 'hostname', in: 'path', schema: { type: 'string' } }],
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
server.registerOperation?.({
|
|
380
|
+
name: 'delete_ssh_key',
|
|
381
|
+
execute: deleteSSHKey,
|
|
382
|
+
httpMethod: 'DELETE',
|
|
383
|
+
parametersSchema: [{ name: 'hostname', in: 'path', schema: { type: 'string' } }],
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
server.registerOperation?.({
|
|
387
|
+
name: 'list_ssh_keys',
|
|
388
|
+
execute: listSSHKeys,
|
|
389
|
+
httpMethod: 'GET',
|
|
390
|
+
parametersSchema: [{ name: 'hostname', in: 'path', schema: { type: 'string' } }],
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
server.registerOperation?.({
|
|
394
|
+
name: 'set_ssh_known_hosts',
|
|
395
|
+
execute: setSSHKnownHosts,
|
|
396
|
+
httpMethod: 'PUT',
|
|
397
|
+
parametersSchema: [{ name: 'hostname', in: 'path', schema: { type: 'string' } }],
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
server.registerOperation?.({
|
|
401
|
+
name: 'get_ssh_known_hosts',
|
|
402
|
+
execute: getSSHKnownHosts,
|
|
403
|
+
httpMethod: 'GET',
|
|
404
|
+
parametersSchema: [{ name: 'hostname', in: 'path', schema: { type: 'string' } }],
|
|
405
|
+
});
|