sliftutils 1.7.133 → 1.7.135
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/bin/addmachine.js +1 -5
- package/bin/derivekey.js +2 -7
- package/bin/portsecuredaemon.js +1 -8
- package/bin/securessh.js +2 -7
- package/bin/setupnotify.js +2 -6
- package/bin/signfiles.js +2 -7
- package/bin/testnotify.js +2 -6
- package/bin/unrevoke.js +1 -5
- package/misc/dist/ownIPs.ts.cache +2 -2
- package/misc/https/dist/certs.ts.cache +12 -9
- package/misc/https/dist/persistentLocalStorage.ts.cache +10 -4
- package/package.json +1 -1
- package/security/authorizedKeys/daemon/daemon.ts +142 -7
- package/security/authorizedKeys/daemon/dist/changes.ts.cache +2 -2
- package/security/authorizedKeys/daemon/dist/git.ts.cache +36 -11
- package/security/authorizedKeys/daemon/dist/notify.ts.cache +2 -2
- package/security/authorizedKeys/daemon/dist/paths.ts.cache +2 -2
- package/security/authorizedKeys/daemon/dist/repoFiles.ts.cache +4 -6
- package/security/authorizedKeys/daemon/dist/revocation.ts.cache +0 -0
- package/security/authorizedKeys/daemon/dist/sessions.ts.cache +2 -2
- package/security/authorizedKeys/daemon/dist/state.ts.cache +2 -2
- package/security/authorizedKeys/daemon/dist/trust.ts.cache +85 -265
- package/security/authorizedKeys/daemon/git.d.ts +19 -2
- package/security/authorizedKeys/daemon/git.ts +32 -7
- package/security/authorizedKeys/daemon/repoFiles.ts +2 -4
- package/security/authorizedKeys/daemon/trust.d.ts +25 -19
- package/security/authorizedKeys/daemon/trust.ts +91 -278
- package/security/authorizedKeys/dist/authorizedKeys.ts.cache +2 -2
- package/security/authorizedKeys/dist/keysRepo.ts.cache +2 -2
- package/security/authorizedKeys/dist/revokeSource.ts.cache +2 -2
- package/security/authorizedKeys/dist/sources.ts.cache +2 -2
- package/security/authorizedKeys/secureSSH.ts +6 -1
- package/security/authorizedKeys/unrevoke.ts +2 -198
- package/security/authorizedKeys/unrevokeCli.ts +204 -0
- package/security/helpers/dist/paths.ts.cache +2 -2
- package/security/helpers/dist/remoteSSH.ts.cache +2 -2
- package/security/helpers/dist/spawn.ts.cache +2 -2
- package/security/helpers/remoteSSH.d.ts +35 -0
- package/security/keys/deriveKey.d.ts +0 -1
- package/security/keys/deriveKey.ts +1 -56
- package/security/keys/deriveKeyCli.ts +62 -0
- package/security/keys/dist/deriveKey.ts.cache +4 -51
- package/security/keys/dist/sshKeyFile.ts.cache +5 -3
- package/security/machines/addMachine.ts +31 -61
- package/security/machines/dist/addMachine.ts.cache +72 -57
- package/security/machines/dist/machines.ts.cache +138 -55
- package/security/machines/machines.d.ts +3 -0
- package/security/machines/machines.ts +139 -58
- package/security/notifications/dist/discord.ts.cache +2 -2
- package/security/notifications/setupNotify.ts +6 -1
- package/security/notifications/testNotify.ts +6 -1
- package/security/signedFiles/dist/manifest.ts.cache +2 -2
- package/security/signedFiles/dist/sshsig.ts.cache +138 -0
- package/security/signedFiles/signFiles.ts +11 -52
- package/security/signedFiles/signFilesCli.ts +54 -0
- package/security/signedFiles/sshsig.d.ts +13 -0
- package/security/signedFiles/sshsig.ts +141 -0
- package/storage/dist/backblaze.ts.cache +10 -2
|
@@ -1,29 +1,13 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
2
|
import os from "os";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import {
|
|
5
|
-
import { allowAddresses, keyFingerprint, normalizeKeys, summarizeKey } from "./authorizedKeys";
|
|
4
|
+
import { keyFingerprint, normalizeKeys } from "./authorizedKeys";
|
|
6
5
|
import { deriveRevokeKey, revokeRepoURL } from "./revokeSource";
|
|
7
6
|
import { findSourceKey } from "./sources";
|
|
8
|
-
import { signRepo } from "../signedFiles/signFiles";
|
|
9
|
-
import { readRepoKeys } from "./authorizedKeys";
|
|
10
|
-
import { expandHome } from "../helpers/paths";
|
|
11
7
|
import { spawnPromise } from "../helpers/spawn";
|
|
12
|
-
import { getMachines, setMachines } from "../machines/machines";
|
|
13
|
-
import { resolveKeysRepo } from "./keysRepo";
|
|
14
8
|
|
|
15
|
-
const UNREVOKES_DIR = "unrevoked";
|
|
9
|
+
export const UNREVOKES_DIR = "unrevoked";
|
|
16
10
|
const REVOCATIONS_DIR = "revocations";
|
|
17
|
-
const GIT_KEYWORD = "git";
|
|
18
|
-
const COMMIT_MESSAGE = "unrevoke keys";
|
|
19
|
-
const USAGE = `Usage: yarn unrevoke [keys-repo] [${GIT_KEYWORD}]
|
|
20
|
-
|
|
21
|
-
Run this in a keys repo, or name one. It reads that repo's revoke repo and writes one unrevoke file
|
|
22
|
-
naming every revocation in it, so the keys are accepted again as each machine picks it up. It signs
|
|
23
|
-
the result, and with ${GIT_KEYWORD} it commits and pushes it too.
|
|
24
|
-
|
|
25
|
-
Keys that were revoked should normally be deleted from the repo instead. Unrevoking only matters
|
|
26
|
-
for a key you still want.`;
|
|
27
11
|
|
|
28
12
|
/** One revocation event. Either an ssh key used from an address it is not allowed from, or a
|
|
29
13
|
machine that talked to us from one. Both are the same shape - an identity and an address - and
|
|
@@ -126,183 +110,3 @@ export async function revokedKeysInRepo(config: { repoPath: string; sourceURL: s
|
|
|
126
110
|
.filter(revocation => keys.some(key => keyFingerprint(key) === revocation.fingerprint))
|
|
127
111
|
.map(revocation => ({ revocation, key: keys.find(key => keyFingerprint(key) === revocation.fingerprint) || "" }));
|
|
128
112
|
}
|
|
129
|
-
|
|
130
|
-
/** Writes the addresses into the from= list of each key in the repo's authorized_keys, and says
|
|
131
|
-
which ones were actually new. Only the lines being changed are touched, so comments, ordering
|
|
132
|
-
and any key not involved come out of this byte for byte the same. */
|
|
133
|
-
async function allowAddressesInRepo(config: {
|
|
134
|
-
repoPath: string;
|
|
135
|
-
addressesByFingerprint: Map<string, string[]>;
|
|
136
|
-
}) {
|
|
137
|
-
let { repoPath, addressesByFingerprint } = config;
|
|
138
|
-
let added = new Map<string, string[]>();
|
|
139
|
-
let filePath = path.join(repoPath, "authorized_keys");
|
|
140
|
-
let contents;
|
|
141
|
-
try {
|
|
142
|
-
contents = await fs.readFile(filePath, "utf8");
|
|
143
|
-
} catch (e) {
|
|
144
|
-
// A repo holding its keys as separate .pub files. Nothing to edit in one place, so the
|
|
145
|
-
// caller's listing is all whoever ran this gets.
|
|
146
|
-
console.log(`No authorized_keys in ${repoPath}, so the addresses were not added to any key.`);
|
|
147
|
-
return added;
|
|
148
|
-
}
|
|
149
|
-
let lines = contents.split("\n");
|
|
150
|
-
for (let index = 0; index < lines.length; index++) {
|
|
151
|
-
let fingerprint = keyFingerprint(lines[index]);
|
|
152
|
-
let addresses = fingerprint && addressesByFingerprint.get(fingerprint);
|
|
153
|
-
if (!addresses || !addresses.length) {
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
let result = allowAddresses(lines[index], addresses);
|
|
157
|
-
lines[index] = result.keyLine;
|
|
158
|
-
added.set(fingerprint, [...(added.get(fingerprint) || []), ...result.added]);
|
|
159
|
-
}
|
|
160
|
-
if ([...added.values()].some(list => list.length)) {
|
|
161
|
-
await fs.writeFile(filePath, lines.join("\n"));
|
|
162
|
-
}
|
|
163
|
-
return added;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/** Puts each machine's addresses back into its own file, which is the machine equivalent of adding
|
|
167
|
-
an address to a key's from= list: without it the machine is allowed again in principle and
|
|
168
|
-
still rejected on sight, which revokes it once more. */
|
|
169
|
-
async function allowAddressesForMachines(config: { repoPath: string; revocations: Revocation[] }) {
|
|
170
|
-
let { repoPath, revocations } = config;
|
|
171
|
-
let added = new Map<string, string[]>();
|
|
172
|
-
// Read the whole list, change what these revocations are about, write it back. Setting is by
|
|
173
|
-
// the whole set, so writing one machine at a time would delete every other machine.
|
|
174
|
-
let machines = await getMachines(repoPath);
|
|
175
|
-
for (let revocation of revocations) {
|
|
176
|
-
let machineId = revocation.machineId;
|
|
177
|
-
let ip = revocationIP(revocation);
|
|
178
|
-
if (!machineId || !ip) {
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
let existing = machines.find(machine => machine.machineId === machineId);
|
|
182
|
-
if (!existing) {
|
|
183
|
-
// Removed from the repo since it was revoked, so there is nothing to allow. Deleting a
|
|
184
|
-
// machine is the stronger statement and it stands.
|
|
185
|
-
console.log(`machines/${machineId}.json is not in this repo, so ${ip} was not added to it`);
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
if (existing.ips.includes(ip)) {
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
existing.ips = [...existing.ips, ip];
|
|
192
|
-
added.set(machineId, [...(added.get(machineId) || []), ip]);
|
|
193
|
-
}
|
|
194
|
-
if (added.size) {
|
|
195
|
-
await setMachines({ repoPath, machines });
|
|
196
|
-
}
|
|
197
|
-
return added;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
export async function main() {
|
|
202
|
-
let argv = process.argv.slice(2);
|
|
203
|
-
let pushToGit = argv.includes(GIT_KEYWORD);
|
|
204
|
-
let positional = argv.filter(arg => arg !== GIT_KEYWORD);
|
|
205
|
-
if (positional.length > 1) {
|
|
206
|
-
throw new Error(`Expected at most a keys repo, was ${positional.length} argument(s)\n${USAGE}`);
|
|
207
|
-
}
|
|
208
|
-
let { repoPath, sourceURL: originURL } = await resolveKeysRepo(positional[0]);
|
|
209
|
-
|
|
210
|
-
let revocations = await readRemoteRevocations({ sourceURL: originURL });
|
|
211
|
-
if (!revocations.length) {
|
|
212
|
-
console.log(`${revokeRepoURL(originURL)} lists no revocations, so there is nothing to undo.`);
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
let directory = path.join(repoPath, UNREVOKES_DIR);
|
|
217
|
-
await fs.mkdir(directory, { recursive: true });
|
|
218
|
-
let stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
219
|
-
let unrevokeId = `${stamp}-unrevoke`;
|
|
220
|
-
await fs.writeFile(path.join(directory, `${unrevokeId}.json`), JSON.stringify({
|
|
221
|
-
unrevokeId,
|
|
222
|
-
createdAt: new Date().toISOString(),
|
|
223
|
-
// What this file says: each of these keys is allowed from this address. Pairs, not keys,
|
|
224
|
-
// and not a blanket "allow everything again" - a key forgiven here for one address is
|
|
225
|
-
// still revoked the moment it is used from another.
|
|
226
|
-
allowed: revocations.map(revocation => ({
|
|
227
|
-
fingerprint: revocation.fingerprint,
|
|
228
|
-
machineId: revocation.machineId,
|
|
229
|
-
ip: revocationIP(revocation),
|
|
230
|
-
})),
|
|
231
|
-
revocations: revocations.map(revocation => ({
|
|
232
|
-
revocationId: revocation.revocationId,
|
|
233
|
-
fingerprint: revocation.fingerprint,
|
|
234
|
-
machineId: revocation.machineId,
|
|
235
|
-
ip: revocationIP(revocation),
|
|
236
|
-
revokedAt: revocation.revokedAt,
|
|
237
|
-
revokedBy: revocation.revokedBy,
|
|
238
|
-
attempt: revocation.attempt,
|
|
239
|
-
})),
|
|
240
|
-
}, undefined, 4) + "\n");
|
|
241
|
-
|
|
242
|
-
// Named the way the notifications name them. A fingerprint is not in authorized_keys and
|
|
243
|
-
// cannot be worked out by eye, so on its own it tells whoever is reading this nothing about
|
|
244
|
-
// which of their keys is being talked about.
|
|
245
|
-
let keysByFingerprint = new Map<string, string>();
|
|
246
|
-
for (let key of await readRepoKeys(repoPath).catch(() => [] as string[])) {
|
|
247
|
-
keysByFingerprint.set(keyFingerprint(key), key);
|
|
248
|
-
}
|
|
249
|
-
let describeKey = (revocation: Revocation) => {
|
|
250
|
-
let key = keysByFingerprint.get(revocation.fingerprint || "") || revocation.key || "";
|
|
251
|
-
return key && summarizeKey(key) || `a key no longer in this repo (${revocation.fingerprint})`;
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
let addressesByIdentity = new Map<string, string[]>();
|
|
255
|
-
for (let revocation of revocations) {
|
|
256
|
-
let ip = revocationIP(revocation);
|
|
257
|
-
let identity = revocationIdentity(revocation);
|
|
258
|
-
let existing = addressesByIdentity.get(identity) || [];
|
|
259
|
-
if (ip && !existing.includes(ip)) {
|
|
260
|
-
existing.push(ip);
|
|
261
|
-
}
|
|
262
|
-
addressesByIdentity.set(identity, existing);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Cancelling the revocation on its own would leave sshd refusing that address, and the machines
|
|
266
|
-
// revoking all over again, so the addresses go into the key's from= list and into the machine's
|
|
267
|
-
// own file here. We are in the repo that owns both, so there is nothing to hand back to whoever
|
|
268
|
-
// ran this.
|
|
269
|
-
let added = await allowAddressesInRepo({ repoPath, addressesByFingerprint: addressesByIdentity });
|
|
270
|
-
let addedForMachines = await allowAddressesForMachines({ repoPath, revocations });
|
|
271
|
-
|
|
272
|
-
console.log("");
|
|
273
|
-
for (let [identity, addresses] of addressesByIdentity) {
|
|
274
|
-
let entries = revocations.filter(entry => revocationIdentity(entry) === identity);
|
|
275
|
-
let machineId = entries[0].machineId;
|
|
276
|
-
console.log(`Unfreezing ${machineId && `machine ${machineId}` || describeKey(entries[0])}`);
|
|
277
|
-
console.log(`Allowing IP ${addresses.join(", ") || "(no address recorded)"}`);
|
|
278
|
-
let addedHere = machineId && addedForMachines.get(machineId) || added.get(identity) || [];
|
|
279
|
-
if (addedHere.length) {
|
|
280
|
-
let where = machineId && `machines/${machineId}.json` || `that key's from= list in authorized_keys`;
|
|
281
|
-
console.log(` added to ${where}: ${addedHere.join(", ")}`);
|
|
282
|
-
} else {
|
|
283
|
-
console.log(` already allowed from ${addresses.join(", ") || "everywhere"}`);
|
|
284
|
-
}
|
|
285
|
-
for (let entry of entries) {
|
|
286
|
-
console.log(
|
|
287
|
-
` frozen by ${entry.revokedBy || "?"} ${entry.revokedAt || "at an unrecorded time"}`
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
console.log("");
|
|
291
|
-
}
|
|
292
|
-
console.log(`Every other address stays frozen, and any of these used from anywhere else is`);
|
|
293
|
-
console.log(`frozen again.`);
|
|
294
|
-
|
|
295
|
-
// Signed here rather than left as a step to remember. An unrevoke nobody signed does nothing at
|
|
296
|
-
// all, and the repo would sit there looking done while every machine ignored it.
|
|
297
|
-
await signRepo({ repoPath });
|
|
298
|
-
|
|
299
|
-
if (!pushToGit) {
|
|
300
|
-
console.log(`\nCommit and push it, and those keys work again as each machine picks it up:`);
|
|
301
|
-
console.log(`\`\`\`\ngit add -A\ngit commit -m "${COMMIT_MESSAGE}"\ngit push\n\`\`\``);
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
await runPromise(`git add -A`, { cwd: repoPath });
|
|
305
|
-
await runPromise(`git commit -m "${COMMIT_MESSAGE}"`, { cwd: repoPath });
|
|
306
|
-
await runPromise(`git push`, { cwd: repoPath });
|
|
307
|
-
console.log(`\nCommitted and pushed. Those keys work again as each machine picks it up.`);
|
|
308
|
-
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { runPromise } from "socket-function/src/runPromise";
|
|
4
|
+
import { allowAddresses, keyFingerprint, readRepoKeys, summarizeKey } from "./authorizedKeys";
|
|
5
|
+
import { revokeRepoURL } from "./revokeSource";
|
|
6
|
+
import { signRepo } from "../signedFiles/signFiles";
|
|
7
|
+
import { getMachines, setMachines } from "../machines/machines";
|
|
8
|
+
import { resolveKeysRepo } from "./keysRepo";
|
|
9
|
+
import { readRemoteRevocations, Revocation, revocationIdentity, revocationIP, UNREVOKES_DIR } from "./unrevoke";
|
|
10
|
+
|
|
11
|
+
const GIT_KEYWORD = "git";
|
|
12
|
+
const COMMIT_MESSAGE = "unrevoke keys";
|
|
13
|
+
const USAGE = `Usage: yarn unrevoke [keys-repo] [${GIT_KEYWORD}]
|
|
14
|
+
|
|
15
|
+
Run this in a keys repo, or name one. It reads that repo's revoke repo and writes one unrevoke file
|
|
16
|
+
naming every revocation in it, so the keys are accepted again as each machine picks it up. It signs
|
|
17
|
+
the result, and with ${GIT_KEYWORD} it commits and pushes it too.
|
|
18
|
+
|
|
19
|
+
Keys that were revoked should normally be deleted from the repo instead. Unrevoking only matters
|
|
20
|
+
for a key you still want.`;
|
|
21
|
+
|
|
22
|
+
/** Writes the addresses into the from= list of each key in the repo's authorized_keys, and says
|
|
23
|
+
which ones were actually new. Only the lines being changed are touched, so comments, ordering
|
|
24
|
+
and any key not involved come out of this byte for byte the same. */
|
|
25
|
+
async function allowAddressesInRepo(config: {
|
|
26
|
+
repoPath: string;
|
|
27
|
+
addressesByFingerprint: Map<string, string[]>;
|
|
28
|
+
}) {
|
|
29
|
+
let { repoPath, addressesByFingerprint } = config;
|
|
30
|
+
let added = new Map<string, string[]>();
|
|
31
|
+
let filePath = path.join(repoPath, "authorized_keys");
|
|
32
|
+
let contents;
|
|
33
|
+
try {
|
|
34
|
+
contents = await fs.readFile(filePath, "utf8");
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// A repo holding its keys as separate .pub files. Nothing to edit in one place, so the
|
|
37
|
+
// caller's listing is all whoever ran this gets.
|
|
38
|
+
console.log(`No authorized_keys in ${repoPath}, so the addresses were not added to any key.`);
|
|
39
|
+
return added;
|
|
40
|
+
}
|
|
41
|
+
let lines = contents.split("\n");
|
|
42
|
+
for (let index = 0; index < lines.length; index++) {
|
|
43
|
+
let fingerprint = keyFingerprint(lines[index]);
|
|
44
|
+
let addresses = fingerprint && addressesByFingerprint.get(fingerprint);
|
|
45
|
+
if (!addresses || !addresses.length) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
let result = allowAddresses(lines[index], addresses);
|
|
49
|
+
lines[index] = result.keyLine;
|
|
50
|
+
added.set(fingerprint, [...(added.get(fingerprint) || []), ...result.added]);
|
|
51
|
+
}
|
|
52
|
+
if ([...added.values()].some(list => list.length)) {
|
|
53
|
+
await fs.writeFile(filePath, lines.join("\n"));
|
|
54
|
+
}
|
|
55
|
+
return added;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Puts each machine's addresses back into its own file, which is the machine equivalent of adding
|
|
59
|
+
an address to a key's from= list: without it the machine is allowed again in principle and
|
|
60
|
+
still rejected on sight, which revokes it once more. */
|
|
61
|
+
async function allowAddressesForMachines(config: { repoPath: string; revocations: Revocation[] }) {
|
|
62
|
+
let { repoPath, revocations } = config;
|
|
63
|
+
let added = new Map<string, string[]>();
|
|
64
|
+
// Read the whole list, change what these revocations are about, write it back. Setting is by
|
|
65
|
+
// the whole set, so writing one machine at a time would delete every other machine.
|
|
66
|
+
let machines = await getMachines(repoPath);
|
|
67
|
+
for (let revocation of revocations) {
|
|
68
|
+
let machineId = revocation.machineId;
|
|
69
|
+
let ip = revocationIP(revocation);
|
|
70
|
+
if (!machineId || !ip) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
let existing = machines.find(machine => machine.machineId === machineId);
|
|
74
|
+
if (!existing) {
|
|
75
|
+
// Removed from the repo since it was revoked, so there is nothing to allow. Deleting a
|
|
76
|
+
// machine is the stronger statement and it stands.
|
|
77
|
+
console.log(`machines/${machineId}.json is not in this repo, so ${ip} was not added to it`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (existing.ips.includes(ip)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
existing.ips = [...existing.ips, ip];
|
|
84
|
+
added.set(machineId, [...(added.get(machineId) || []), ip]);
|
|
85
|
+
}
|
|
86
|
+
if (added.size) {
|
|
87
|
+
await setMachines({ repoPath, machines });
|
|
88
|
+
}
|
|
89
|
+
return added;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function main() {
|
|
93
|
+
let argv = process.argv.slice(2);
|
|
94
|
+
let pushToGit = argv.includes(GIT_KEYWORD);
|
|
95
|
+
let positional = argv.filter(arg => arg !== GIT_KEYWORD);
|
|
96
|
+
if (positional.length > 1) {
|
|
97
|
+
throw new Error(`Expected at most a keys repo, was ${positional.length} argument(s)\n${USAGE}`);
|
|
98
|
+
}
|
|
99
|
+
let { repoPath, sourceURL: originURL } = await resolveKeysRepo(positional[0]);
|
|
100
|
+
|
|
101
|
+
let revocations = await readRemoteRevocations({ sourceURL: originURL });
|
|
102
|
+
if (!revocations.length) {
|
|
103
|
+
console.log(`${revokeRepoURL(originURL)} lists no revocations, so there is nothing to undo.`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let directory = path.join(repoPath, UNREVOKES_DIR);
|
|
108
|
+
await fs.mkdir(directory, { recursive: true });
|
|
109
|
+
let stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
110
|
+
let unrevokeId = `${stamp}-unrevoke`;
|
|
111
|
+
await fs.writeFile(path.join(directory, `${unrevokeId}.json`), JSON.stringify({
|
|
112
|
+
unrevokeId,
|
|
113
|
+
createdAt: new Date().toISOString(),
|
|
114
|
+
// What this file says: each of these keys is allowed from this address. Pairs, not keys,
|
|
115
|
+
// and not a blanket "allow everything again" - a key forgiven here for one address is
|
|
116
|
+
// still revoked the moment it is used from another.
|
|
117
|
+
allowed: revocations.map(revocation => ({
|
|
118
|
+
fingerprint: revocation.fingerprint,
|
|
119
|
+
machineId: revocation.machineId,
|
|
120
|
+
ip: revocationIP(revocation),
|
|
121
|
+
})),
|
|
122
|
+
revocations: revocations.map(revocation => ({
|
|
123
|
+
revocationId: revocation.revocationId,
|
|
124
|
+
fingerprint: revocation.fingerprint,
|
|
125
|
+
machineId: revocation.machineId,
|
|
126
|
+
ip: revocationIP(revocation),
|
|
127
|
+
revokedAt: revocation.revokedAt,
|
|
128
|
+
revokedBy: revocation.revokedBy,
|
|
129
|
+
attempt: revocation.attempt,
|
|
130
|
+
})),
|
|
131
|
+
}, undefined, 4) + "\n");
|
|
132
|
+
|
|
133
|
+
// Named the way the notifications name them. A fingerprint is not in authorized_keys and
|
|
134
|
+
// cannot be worked out by eye, so on its own it tells whoever is reading this nothing about
|
|
135
|
+
// which of their keys is being talked about.
|
|
136
|
+
let keysByFingerprint = new Map<string, string>();
|
|
137
|
+
for (let key of await readRepoKeys(repoPath).catch(() => [] as string[])) {
|
|
138
|
+
keysByFingerprint.set(keyFingerprint(key), key);
|
|
139
|
+
}
|
|
140
|
+
let describeKey = (revocation: Revocation) => {
|
|
141
|
+
let key = keysByFingerprint.get(revocation.fingerprint || "") || revocation.key || "";
|
|
142
|
+
return key && summarizeKey(key) || `a key no longer in this repo (${revocation.fingerprint})`;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
let addressesByIdentity = new Map<string, string[]>();
|
|
146
|
+
for (let revocation of revocations) {
|
|
147
|
+
let ip = revocationIP(revocation);
|
|
148
|
+
let identity = revocationIdentity(revocation);
|
|
149
|
+
let existing = addressesByIdentity.get(identity) || [];
|
|
150
|
+
if (ip && !existing.includes(ip)) {
|
|
151
|
+
existing.push(ip);
|
|
152
|
+
}
|
|
153
|
+
addressesByIdentity.set(identity, existing);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Cancelling the revocation on its own would leave sshd refusing that address, and the machines
|
|
157
|
+
// revoking all over again, so the addresses go into the key's from= list and into the machine's
|
|
158
|
+
// own file here. We are in the repo that owns both, so there is nothing to hand back to whoever
|
|
159
|
+
// ran this.
|
|
160
|
+
let added = await allowAddressesInRepo({ repoPath, addressesByFingerprint: addressesByIdentity });
|
|
161
|
+
let addedForMachines = await allowAddressesForMachines({ repoPath, revocations });
|
|
162
|
+
|
|
163
|
+
console.log("");
|
|
164
|
+
for (let [identity, addresses] of addressesByIdentity) {
|
|
165
|
+
let entries = revocations.filter(entry => revocationIdentity(entry) === identity);
|
|
166
|
+
let machineId = entries[0].machineId;
|
|
167
|
+
console.log(`Unfreezing ${machineId && `machine ${machineId}` || describeKey(entries[0])}`);
|
|
168
|
+
console.log(`Allowing IP ${addresses.join(", ") || "(no address recorded)"}`);
|
|
169
|
+
let addedHere = machineId && addedForMachines.get(machineId) || added.get(identity) || [];
|
|
170
|
+
if (addedHere.length) {
|
|
171
|
+
let where = machineId && `machines/${machineId}.json` || `that key's from= list in authorized_keys`;
|
|
172
|
+
console.log(` added to ${where}: ${addedHere.join(", ")}`);
|
|
173
|
+
} else {
|
|
174
|
+
console.log(` already allowed from ${addresses.join(", ") || "everywhere"}`);
|
|
175
|
+
}
|
|
176
|
+
for (let entry of entries) {
|
|
177
|
+
console.log(
|
|
178
|
+
` frozen by ${entry.revokedBy || "?"} ${entry.revokedAt || "at an unrecorded time"}`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
console.log("");
|
|
182
|
+
}
|
|
183
|
+
console.log(`Every other address stays frozen, and any of these used from anywhere else is`);
|
|
184
|
+
console.log(`frozen again.`);
|
|
185
|
+
|
|
186
|
+
// Signed here rather than left as a step to remember. An unrevoke nobody signed does nothing at
|
|
187
|
+
// all, and the repo would sit there looking done while every machine ignored it.
|
|
188
|
+
await signRepo({ repoPath });
|
|
189
|
+
|
|
190
|
+
if (!pushToGit) {
|
|
191
|
+
console.log(`\nCommit and push it, and those keys work again as each machine picks it up:`);
|
|
192
|
+
console.log(`\`\`\`\ngit add -A\ngit commit -m "${COMMIT_MESSAGE}"\ngit push\n\`\`\``);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
await runPromise(`git add -A`, { cwd: repoPath });
|
|
196
|
+
await runPromise(`git commit -m "${COMMIT_MESSAGE}"`, { cwd: repoPath });
|
|
197
|
+
await runPromise(`git push`, { cwd: repoPath });
|
|
198
|
+
console.log(`\nCommitted and pushed. Those keys work again as each machine picks it up.`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
main().catch(e => {
|
|
202
|
+
console.error(`${e}`);
|
|
203
|
+
process.exitCode = 1;
|
|
204
|
+
}).finally(() => process.exit());
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict"; var console = (function shimConsole() { let baseConsole = globalThis.console; if (typeof window !== "undefined") return baseConsole; let name = __filename.replaceAll("\\", "/").split("/").pop(); let dirName = __dirname.replaceAll("\\", "/").split("/").pop(); let context = { __FILE__: __filename, __DIR__: __dirname, __NAME__: name, __DIRNAME__: dirName }; let newConsole = { ...baseConsole }; for (let shim of ["log", "info", "warn", "error"]) { newConsole[shim] = (...args) => { args.push(context); baseConsole[shim](...args); }; } newConsole.__context = context; return newConsole; })(); "use strict";
|
|
2
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule && mod.default) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
@@ -25,4 +25,4 @@ function expandHome(filePath) {
|
|
|
25
25
|
}
|
|
26
26
|
exports.expandHome = expandHome;
|
|
27
27
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGF0aHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJwYXRocy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQSw0Q0FBb0I7QUFDcEIsZ0RBQXdCO0FBRXhCOztnQ0FFZ0M7QUFDaEMsU0FBZ0IsVUFBVSxDQUFDLFFBQWdCO0lBQ3ZDLElBQUksUUFBUSxLQUFLLEdBQUcsRUFBRSxDQUFDO1FBQ25CLE9BQU8sWUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ3hCLENBQUM7SUFDRCxJQUFJLFFBQVEsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLElBQUksUUFBUSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQzFELE9BQU8sY0FBSSxDQUFDLElBQUksQ0FBQyxZQUFFLENBQUMsT0FBTyxFQUFFLEVBQUUsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3RELENBQUM7SUFDRCxJQUFJLFFBQVEsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUMzQix5RkFBeUY7UUFDekYsa0RBQWtEO1FBQ2xELE1BQU0sSUFBSSxLQUFLLENBQUMsdUNBQXVDLFFBQVEsaUVBQWlFLENBQUMsQ0FBQztJQUN0SSxDQUFDO0lBQ0QsT0FBTyxjQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQ2xDLENBQUM7QUFiRCxnQ0FhQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBvcyBmcm9tIFwib3NcIjtcbmltcG9ydCBwYXRoIGZyb20gXCJwYXRoXCI7XG5cbi8qKiBXaW5kb3dzIHNoZWxscyBkbyBub3QgZXhwYW5kIH4gdGhlbXNlbHZlcywgYW5kIGh1bnRpbmcgZG93biB5b3VyIGhvbWUgZm9sZGVyIGJ5IGhhbmQgaXNcbiAgICBhbm5veWluZywgc28gd2UgZXhwYW5kIGl0IG9uIGV2ZXJ5IHBsYXRmb3JtLiBCb3RoIHNlcGFyYXRvcnMgYXJlIGFjY2VwdGVkLCBiZWNhdXNlIGEgV2luZG93c1xuICAgIHVzZXIgbWF5IHR5cGUgZWl0aGVyIG9uZS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBleHBhbmRIb21lKGZpbGVQYXRoOiBzdHJpbmcpIHtcbiAgICBpZiAoZmlsZVBhdGggPT09IFwiflwiKSB7XG4gICAgICAgIHJldHVybiBvcy5ob21lZGlyKCk7XG4gICAgfVxuICAgIGlmIChmaWxlUGF0aC5zdGFydHNXaXRoKFwifi9cIikgfHwgZmlsZVBhdGguc3RhcnRzV2l0aChcIn5cXFxcXCIpKSB7XG4gICAgICAgIHJldHVybiBwYXRoLmpvaW4ob3MuaG9tZWRpcigpLCBmaWxlUGF0aC5zbGljZSgyKSk7XG4gICAgfVxuICAgIGlmIChmaWxlUGF0aC5zdGFydHNXaXRoKFwiflwiKSkge1xuICAgICAgICAvLyB+b3RoZXJ1c2VyIG5lZWRzIGFuIGFjY291bnQgZGF0YWJhc2Ugd2UgY2Fubm90IHJlYWQgcG9ydGFibHksIGFuZCBxdWlldGx5IHJlc29sdmluZyBpdFxuICAgICAgICAvLyB0byB0aGUgd3JvbmcgaG9tZSB3b3VsZCBiZSB3b3JzZSB0aGFuIHJlZnVzaW5nLlxuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIH4gb3IgYW4gb3JkaW5hcnkgcGF0aCwgd2FzICR7ZmlsZVBhdGh9LiBSZWZlcnJpbmcgdG8gYW5vdGhlciB1c2VyJ3MgaG9tZSB3aXRoIH5uYW1lIGlzIG5vdCBzdXBwb3J0ZWQuYCk7XG4gICAgfVxuICAgIHJldHVybiBwYXRoLnJlc29sdmUoZmlsZVBhdGgpO1xufVxuIl19
|
|
28
|
-
/* _JS_SOURCE_HASH = "
|
|
28
|
+
/* _JS_SOURCE_HASH = "493654cc4781cc00e46bb1f87e8f539ef3884ca93d7ce6173bbd46a2bd648be8"; */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict"; var console = (function shimConsole() { let baseConsole = globalThis.console; if (typeof window !== "undefined") return baseConsole; let name = __filename.replaceAll("\\", "/").split("/").pop(); let dirName = __dirname.replaceAll("\\", "/").split("/").pop(); let context = { __FILE__: __filename, __DIR__: __dirname, __NAME__: name, __DIRNAME__: dirName }; let newConsole = { ...baseConsole }; for (let shim of ["log", "info", "warn", "error"]) { newConsole[shim] = (...args) => { args.push(context); baseConsole[shim](...args); }; } newConsole.__context = context; return newConsole; })(); "use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
|
|
3
3
|
//exports.remoteCommandExists = exports.writeRemoteFile = exports.readRemoteFile = exports.runOverSSH = exports.describeHost = exports.THIS_MACHINE = exports.SUDO_PREAMBLE = void 0;
|
|
4
4
|
const spawn_1 = require("./spawn");
|
|
@@ -97,4 +97,4 @@ async function remoteCommandExists(config) {
|
|
|
97
97
|
}
|
|
98
98
|
exports.remoteCommandExists = remoteCommandExists;
|
|
99
99
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVtb3RlU1NILmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsicmVtb3RlU1NILnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLG1DQUF1QztBQUV2QyxNQUFNLG1CQUFtQixHQUFHLEVBQUUsQ0FBQztBQUMvQixNQUFNLGFBQWEsR0FBRyx1QkFBdUIsQ0FBQztBQUM5QyxNQUFNLGNBQWMsR0FBRyx3QkFBd0IsQ0FBQztBQUNoRCxNQUFNLHFCQUFxQixHQUFHLEdBQUcsQ0FBQztBQUVsQyw4RUFBOEU7QUFDakUsUUFBQSxhQUFhLEdBQUcsMkRBQTJELENBQUM7QUFFekY7OzZCQUU2QjtBQUNoQixRQUFBLFlBQVksR0FBRyxFQUFFLENBQUM7QUFFL0IsU0FBZ0IsWUFBWSxDQUFDLElBQVk7SUFDckMsT0FBTyxJQUFJLElBQUksY0FBYyxDQUFDO0FBQ2xDLENBQUM7QUFGRCxvQ0FFQztBQUVEO3dGQUN3RjtBQUNqRixLQUFLLFVBQVUsVUFBVSxDQUFDLE1BQWdGO0lBQzdHLElBQUksRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxZQUFZLEVBQUUsR0FBRyxNQUFNLENBQUM7SUFDbkQsSUFBSSxNQUFNLEdBQUcsTUFBTSxJQUFBLG9CQUFZLEVBQUM7UUFDNUIsT0FBTyxFQUFFLElBQUksSUFBSSxLQUFLLElBQUksSUFBSTtRQUM5QixJQUFJLEVBQUUsSUFBSSxJQUFJO1lBQ1YsSUFBSSxFQUFFLGVBQWU7WUFDckIsSUFBSSxFQUFFLGtCQUFrQixtQkFBbUIsRUFBRTtZQUM3QyxJQUFJO1lBQ0osTUFBTTtTQUNULElBQUksQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDO1FBQ25CLEtBQUs7UUFDTCxhQUFhLEVBQUUsQ0FBQyxZQUFZO0tBQy9CLENBQUMsQ0FBQztJQUNILElBQUksTUFBTSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ2YsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsWUFBWSxDQUFDLElBQUksQ0FBQyxpQkFBaUIs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LEdBQUcsTUFBTSxVQUFVLENBQUM7UUFDMUIsSUFBSTtRQUNKLE1BQU0sRUFBRSxlQUFlLE9BQU8sMkNBQTJDO1FBQ3pFLFlBQVksRUFBRSxJQUFJO0tBQ3JCLENBQUMsQ0FBQztJQUNILE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsS0FBSyxLQUFLLENBQUM7QUFDMUMsQ0FBQztBQVJELGtEQVFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgc3Bhd25Qcm9taXNlIH0gZnJvbSBcIi4vc3Bhd25cIjtcblxuY29uc3QgU1NIX0NPTk5FQ1RfVElNRU9VVCA9IDEwO1xuY29uc3QgRVhJU1RTX01BUktFUiA9IFwiX19QT1JUU0VDVVJFX0VYSVNUU19fXCI7XG5jb25zdCBNSVNTSU5HX01BUktFUiA9IFwiX19QT1JUU0VDVVJFX01JU1NJTkdfX1wiO1xuY29uc3QgTUFYX0VSUk9SX0JPRFlfTEVOR1RIID0gNTAwO1xuXG4vLyAvZXRjIGlzIHJvb3Qgb3duZWQsIHNvIGZhbGwgYmFjayB0byBzdWRvIHdoZW5ldmVyIHRoZSBTU0ggdXNlciBpcyBub3Qgcm9vdC5cbmV4cG9ydCBjb25zdCBTVURPX1BSRUFNQkxFID0gYFNVRE89XCJcIjsgaWYgWyBcIiQoaWQgLXUpXCIgLW5lIDAgXTsgdGhlbiBTVURPPVwic3VkbyAtblwiOyBmaWA7XG5cbi8qKiBObyBob3N0IG1lYW5zIHRoaXMgbWFjaGluZSwgc28gdGhlIHZlcnkgc2FtZSBzY3JpcHQgcnVucyB3aXRoIG5vdGhpbmcgaW4gZnJvbnQgb2YgaXQuIExvY2FsIGFuZFxuICAgIHJlbW90ZSB0aGVuIGRvIGV4YWN0bHkgdGhlIHNhbWUgdGhpbmcsIHJhdGhlciB0aGFuIGJlaW5nIHR3byBwaWVjZXMgb2YgY29kZSB0aGF0IGhhdmUgdG8gYmUga2VwdFxuICAgIHNheWluZyB0aGUgc2FtZSB0aGluZy4gKi9cbmV4cG9ydCBjb25zdCBUSElTX01BQ0hJTkUgPSBcIlwiO1xuXG5leHBvcnQgZnVuY3Rpb24gZGVzY3JpYmVIb3N0KGhvc3Q6IHN0cmluZykge1xuICAgIHJldHVybiBob3N0IHx8IFwidGhpcyBtYWNoaW5lXCI7XG59XG5cbi8qKiBUaGUgaG9zdCBzdHJpbmcgaXMgaGFuZGVkIHRvIHNzaCB1bnRvdWNoZWQuIFVzZXJzLCBrZXlzIGFuZCBwb3J0cyBiZWxvbmcgaW4gdGhlIGNhbGxlcidzIHNzaFxuICAgIGNvbmZpZywgc28gQmF0Y2hNb2RlIG1ha2VzIGEgbWlzc2luZyBzZXR1cCBmYWlsIGltbWVkaWF0ZWx5IGluc3RlYWQgb2YgcHJvbXB0aW5nLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJ1bk92ZXJTU0goY29uZmlnOiB7IGhvc3Q6IHN0cmluZzsgc2NyaXB0OiBzdHJpbmc7IGlucHV0Pzogc3RyaW5nOyBhbGxvd0ZhaWx1cmU/OiBib29sZWFuIH0pIHtcbiAgICBsZXQgeyBob3N0LCBzY3JpcHQsIGlucHV0LCBhbGxvd0ZhaWx1cmUgfSA9IGNvbmZpZztcbiAgICBsZXQgcmVzdWx0ID0gYXdhaXQgc3Bhd25Qcm9taXNlKHtcbiAgICAgICAgY29tbWFuZDogaG9zdCAmJiBcInNzaFwiIHx8IFwic2hcIixcbiAgICAgICAgYXJnczogaG9zdCAmJiBbXG4gICAgICAgICAgICBcIi1vXCIsIFwiQmF0Y2hNb2RlPXllc1wiLFxuICAgICAgICAgICAgXCItb1wiLCBgQ29ubmVjdFRpbWVvdXQ9JHtTU0hfQ09OTkVDVF9USU1FT1VUfWAsXG4gICAgICAgICAgICBob3N0LFxuICAgICAgICAgICAgc2NyaXB0LFxuICAgICAgICBdIHx8IFtcIi1jXCIsIHNjcmlwdF0sXG4gICAgICAgIGlucHV0LFxuICAgICAgICBpbmhlcml0U3RkZXJyOiAhYWxsb3dGYWlsdXJlLFxuICAgIH0pO1xuICAgIGlmIChyZXN1bHQuZXJyb3IpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCB0byBydW4gYWdhaW5zdCAke2Rlc2NyaWJlSG9zdChob3N0KX0sIGZhaWxlZCB3aXRoICR7cmVzdWx0LmVycm9yLm1lc3NhZ2V9YCk7XG4gICAgfVxuICAgIGlmIChyZXN1bHQuc3RhdHVzICE9PSAwICYmICFhbGxvd0ZhaWx1cmUpIHtcbiAgICAgICAgbGV0IGFkdmljZSA9IGhvc3RcbiAgICAgICAgICAgICYmIGAgTm9uLWludGVyYWN0aXZlIHNzaCBhY2Nlc3MgdG8gJHtob3N0fSBoYXMgdG8gd29yayBvbiBpdHMgb3duIC0gZml4IGl0IGluIHlvdXIgc3NoIGNvbmZpZy5gXG4gICAgICAgICAgICB8fCBcIlwiO1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBgRXhwZWN0ZWQgdGhlIHNjcmlwdCBvbiAke2Rlc2NyaWJlSG9zdChob3N0KX0gdG8gZXhpdCAwLCB3YXMgJHtyZXN1bHQuc3RhdHVzfS4ke2FkdmljZX1cXG5gXG4gICAgICAgICAgICArIGAkeyhyZXN1bHQuc3Rkb3V0ICsgcmVzdWx0LnN0ZGVycikudHJpbSgpLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCl9YFxuICAgICAgICApO1xuICAgIH1cbiAgICByZXR1cm4geyBzdGRvdXQ6IHJlc3VsdC5zdGRvdXQgfHwgXCJcIiwgc3RkZXJyOiByZXN1bHQuc3RkZXJyIHx8IFwiXCIsIHN0YXR1czogcmVzdWx0LnN0YXR1cyB9O1xufVxuXG4vKiogUmV0dXJucyB1bmRlZmluZWQgd2hlbiB0aGUgZmlsZSBkb2VzIG5vdCBleGlzdCwgc28gYSBtaXNzaW5nIGZpbGUgcmVhZHMgZGlmZmVyZW50bHkgZnJvbSBhblxuICAgIGVtcHR5IG9uZS4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByZWFkUmVtb3RlRmlsZShjb25maWc6IHsgaG9zdDogc3RyaW5nOyBmaWxlUGF0aDogc3RyaW5nIH0pIHtcbiAgICBsZXQgeyBob3N0LCBmaWxlUGF0aCB9ID0gY29uZmlnO1xuICAgIGxldCBvdXRwdXQgPSAoYXdhaXQgcnVuT3ZlclNTSCh7XG4gICAgICAgIGhvc3QsXG4gICAgICAgIHNjcmlwdDogYCR7U1VET19QUkVBTUJMRX1cbmlmICRTVURPIHRlc3QgLWYgXCIke2ZpbGVQYXRofVwiOyB0aGVuXG4gICAgZWNobyBcIiR7RVhJU1RTX01BUktFUn1cIlxuICAgICRTVURPIGNhdCBcIiR7ZmlsZVBhdGh9XCJcbmVsc2VcbiAgICBlY2hvIFwiJHtNSVNTSU5HX01BUktFUn1cIlxuZmlgLFxuICAgIH0pKS5zdGRvdXQ7XG4gICAgbGV0IG5ld2xpbmVJbmRleCA9IG91dHB1dC5pbmRleE9mKFwiXFxuXCIpO1xuICAgIGxldCBtYXJrZXIgPSBvdXRwdXQuc2xpY2UoMCwgbmV3bGluZUluZGV4KS50cmltKCk7XG4gICAgaWYgKG1hcmtlciA9PT0gTUlTU0lOR19NQVJLRVIpIHtcbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG4gICAgaWYgKG1hcmtlciAhPT0gRVhJU1RTX01BUktFUikge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkICR7RVhJU1RTX01BUktFUn0gb3IgJHtNSVNTSU5HX01BUktFUn0gZnJvbSAke2hvc3R9LCB3YXMgJHtvdXRwdXQuc2xpY2UoMCwgTUFYX0VSUk9SX0JPRFlfTEVOR1RIKX1gKTtcbiAgICB9XG4gICAgcmV0dXJuIG91dHB1dC5zbGljZShuZXdsaW5lSW5kZXggKyAxKTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHdyaXRlUmVtb3RlRmlsZShjb25maWc6IHtcbiAgICBob3N0OiBzdHJpbmc7XG4gICAgZmlsZVBhdGg6IHN0cmluZztcbiAgICBjb250ZW50czogc3RyaW5nO1xuICAgIGZpbGVNb2RlOiBzdHJpbmc7XG4gICAgZGlyZWN0b3J5TW9kZTogc3RyaW5nO1xufSkge1xuICAgIGxldCB7IGhvc3QsIGZpbGVQYXRoLCBjb250ZW50cywgZmlsZU1vZGUsIGRpcmVjdG9yeU1vZGUgfSA9IGNvbmZpZztcbiAgICBsZXQgZGlyZWN0b3J5ID0gZmlsZVBhdGguc2xpY2UoMCwgZmlsZVBhdGgubGFzdEluZGV4T2YoXCIvXCIpKTtcbiAgICBhd2FpdCBydW5PdmVyU1NIKHtcbiAgICAgICAgaG9zdCxcbiAgICAgICAgLy8gRGVwbG95aW5nIGZyb20gV2luZG93cyBtdXN0IG5vdCBjYXJyeSBDUkxGIG9udG8gdGhlIHRhcmdldCwgd2hlcmUgaXQgYnJlYWtzIHN5c3RlbWRcbiAgICAgICAgLy8gdW5pdHMgYW5kIG1ha2VzIHByaXZhdGUga2V5cyB1bnJlYWRhYmxlIHRvIHNzaC5cbiAgICAgICAgaW5wdXQ6IGNvbnRlbnRzLnJlcGxhY2UoL1xcclxcbi9nLCBcIlxcblwiKSxcbiAgICAgICAgc2NyaXB0OiBgJHtTVURPX1BSRUFNQkxFfVxuc2V0IC1lXG4kU1VETyBta2RpciAtcCBcIiR7ZGlyZWN0b3J5fVwiXG4kU1VETyBjaG1vZCAke2RpcmVjdG9yeU1vZGV9IFwiJHtkaXJlY3Rvcnl9XCJcbiRTVURPIHRlZSBcIiR7ZmlsZVBhdGh9XCIgPiAvZGV2L251bGxcbiRTVURPIGNobW9kICR7ZmlsZU1vZGV9IFwiJHtmaWxlUGF0aH1cImAsXG4gICAgfSk7XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByZW1vdGVDb21tYW5kRXhpc3RzKGNvbmZpZzogeyBob3N0OiBzdHJpbmc7IGNvbW1hbmQ6IHN0cmluZyB9KSB7XG4gICAgbGV0IHsgaG9zdCwgY29tbWFuZCB9ID0gY29uZmlnO1xuICAgIGxldCByZXN1bHQgPSBhd2FpdCBydW5PdmVyU1NIKHtcbiAgICAgICAgaG9zdCxcbiAgICAgICAgc2NyaXB0OiBgY29tbWFuZCAtdiBcIiR7Y29tbWFuZH1cIiA+IC9kZXYvbnVsbCAyPiYxICYmIGVjaG8geWVzIHx8IGVjaG8gbm9gLFxuICAgICAgICBhbGxvd0ZhaWx1cmU6IHRydWUsXG4gICAgfSk7XG4gICAgcmV0dXJuIHJlc3VsdC5zdGRvdXQudHJpbSgpID09PSBcInllc1wiO1xufVxuIl19
|
|
100
|
-
/* _JS_SOURCE_HASH = "
|
|
100
|
+
/* _JS_SOURCE_HASH = "2bb084f8935f8ec03fde1d65be1a14ffcc51f8bbfefc04b9363fea0639a2a721"; */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict"; var console = (function shimConsole() { let baseConsole = globalThis.console; if (typeof window !== "undefined") return baseConsole; let name = __filename.replaceAll("\\", "/").split("/").pop(); let dirName = __dirname.replaceAll("\\", "/").split("/").pop(); let context = { __FILE__: __filename, __DIR__: __dirname, __NAME__: name, __DIRNAME__: dirName }; let newConsole = { ...baseConsole }; for (let shim of ["log", "info", "warn", "error"]) { newConsole[shim] = (...args) => { args.push(context); baseConsole[shim](...args); }; } newConsole.__context = context; return newConsole; })(); "use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
|
|
3
3
|
//exports.spawnPromise = void 0;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
@@ -31,4 +31,4 @@ function spawnPromise(config) {
|
|
|
31
31
|
}
|
|
32
32
|
exports.spawnPromise = spawnPromise;
|
|
33
33
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3Bhd24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJzcGF3bi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxpREFBc0M7QUFFdEMsMEhBQTBIO0FBQzFILGtGQUFrRjtBQUVsRjs7MEZBRTBGO0FBQzFGLFNBQWdCLFlBQVksQ0FBQyxNQU81QjtJQUNHLElBQUksRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsYUFBYSxFQUFFLFdBQVcsRUFBRSxHQUFHLE1BQU0sQ0FBQztJQUN2RSxPQUFPLElBQUksT0FBTyxDQUEyRixPQUFPLENBQUMsRUFBRTs7UUFDbkgsSUFBSSxLQUFLLEdBQUcsSUFBQSxxQkFBSyxFQUFDLE9BQU8sRUFBRSxJQUFJLEVBQUU7WUFDN0IsR0FBRztZQUNILEtBQUssRUFBRSxDQUFDLE1BQU0sRUFBRSxNQUFNLEVBQUUsYUFBYSxJQUFJLFNBQVMsSUFBSSxNQUFNLENBQUM7U0FDaEUsQ0FBQyxDQUFDO1FBQ0gsSUFBSSxNQUFNLEdBQUcsRUFBRSxDQUFDO1FBQ2hCLElBQUksTUFBTSxHQUFHLEVBQUUsQ0FBQztRQUNoQiw2RUFBNkU7UUFDN0UsSUFBSSxLQUFLLEdBQUcsV0FBVyxJQUFJLFVBQVUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLFdBQVcsQ0FBQyxJQUFJLFNBQVMsQ0FBQztRQUM3RixLQUFLLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxHQUFHLEVBQUUsQ0FBQyxLQUFLLElBQUksWUFBWSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7UUFDdEQsTUFBQSxLQUFLLENBQUMsTUFBTSwwQ0FBRSxFQUFFLENBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQyxFQUFFLENBQUMsTUFBTSxJQUFJLEtBQUssQ0FBQyxDQUFDO1FBQ25ELE1BQUEsS0FBSyxDQUFDLE1BQU0sMENBQUUsRUFBRSxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsRUFBRSxDQUFDLE1BQU0sSUFBSSxLQUFLLENBQUMsQ0FBQztRQUNuRCxLQUFLLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUM7UUFDbEYsS0FBSyxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUFDLEVBQUUsQ0FBQyxPQUFPLENBQUMsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxNQUFNLGFBQU4sTUFBTSxjQUFOLE1BQU0sR0FBSSxTQUFTLEVBQUUsS0FBSyxFQUFFLFNBQVMsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUN4Ryw0RUFBNEU7UUFDNUUsTUFBQSxLQUFLLENBQUMsS0FBSywwQ0FBRSxFQUFFLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzFDLE1BQUEsS0FBSyxDQUFDLEtBQUssMENBQUUsR0FBRyxDQUFDLEtBQUssSUFBSSxFQUFFLENBQUMsQ0FBQztJQUNsQyxDQUFDLENBQUMsQ0FBQztBQUNQLENBQUM7QUEzQkQsb0NBMkJDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgc3Bhd24gfSBmcm9tIFwiY2hpbGRfcHJvY2Vzc1wiO1xuXG4vLyBQT1JURUQgQ09ERTogc2VjdXJpdHkvYXV0aG9yaXplZEtleXMvZGFlbW9uL3BvcnRzZWN1cmVEYWVtb24uanMgY29udGFpbnMgYSBwbGFpbiBKUyBwb3J0IG9mIHNwYXduUHJvbWlzZSwgc28gaXQgY2FuIHJ1blxuLy8gd2l0aCBubyBkZXBlbmRlbmNpZXMuIElmIHlvdSBjaGFuZ2Ugb25lLCBtYWtlIHRoZSBtYXRjaGluZyBjaGFuZ2UgaW4gdGhlIG90aGVyLlxuXG4vKiogQW4gYXJndW1lbnQgbGlzdCByYXRoZXIgdGhhbiBhIHNoZWxsIHN0cmluZywgc28gaG9zdG5hbWVzIGFuZCBwYXRocyBjYW4gbmV2ZXIgYmUgcGFyc2VkIGFzXG4gICAgc2hlbGwgc3ludGF4LiBSZXNvbHZlcyB3aXRoIHRoZSBleGl0IGNvZGUgaW5zdGVhZCBvZiB0aHJvd2luZywgY2FsbGVycyBkZWNpZGUgd2hhdCBhIGZhaWx1cmVcbiAgICBtZWFucy4gYGluaGVyaXRTdGRlcnJgIGxldHMgYSBjaGlsZCdzIG93biBlcnJvcnMgcmVhY2ggdGhlIHRlcm1pbmFsIGFzIHRoZXkgaGFwcGVuLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNwYXduUHJvbWlzZShjb25maWc6IHtcbiAgICBjb21tYW5kOiBzdHJpbmc7XG4gICAgYXJnczogc3RyaW5nW107XG4gICAgY3dkPzogc3RyaW5nO1xuICAgIGlucHV0Pzogc3RyaW5nO1xuICAgIGluaGVyaXRTdGRlcnI/OiBib29sZWFuO1xuICAgIHRpbWVvdXRUaW1lPzogbnVtYmVyO1xufSkge1xuICAgIGxldCB7IGNvbW1hbmQsIGFyZ3MsIGN3ZCwgaW5wdXQsIGluaGVyaXRTdGRlcnIsIHRpbWVvdXRUaW1lIH0gPSBjb25maWc7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlPHsgc3Rkb3V0OiBzdHJpbmc7IHN0ZGVycjogc3RyaW5nOyBzdGF0dXM6IG51bWJlciB8IHVuZGVmaW5lZDsgZXJyb3I6IEVycm9yIHwgdW5kZWZpbmVkIH0+KHJlc29sdmUgPT4ge1xuICAgICAgICBsZXQgY2hpbGQgPSBzcGF3bihjb21tYW5kLCBhcmdzLCB7XG4gICAgICAgICAgICBjd2QsXG4gICAgICAgICAgICBzdGRpbzogW1wicGlwZVwiLCBcInBpcGVcIiwgaW5oZXJpdFN0ZGVyciAmJiBcImluaGVyaXRcIiB8fCBcInBpcGVcIl0sXG4gICAgICAgIH0pO1xuICAgICAgICBsZXQgc3Rkb3V0ID0gXCJcIjtcbiAgICAgICAgbGV0IHN0ZGVyciA9IFwiXCI7XG4gICAgICAgIC8vIEEgY29tbWFuZCB0aGF0IG5ldmVyIHJldHVybnMgd291bGQgb3RoZXJ3aXNlIGhvbGQgdXAgZXZlcnl0aGluZyBiZWhpbmQgaXQuXG4gICAgICAgIGxldCB0aW1lciA9IHRpbWVvdXRUaW1lICYmIHNldFRpbWVvdXQoKCkgPT4gY2hpbGQua2lsbChcIlNJR0tJTExcIiksIHRpbWVvdXRUaW1lKSB8fCB1bmRlZmluZWQ7XG4gICAgICAgIGNoaWxkLm9uKFwiY2xvc2VcIiwgKCkgPT4gdGltZXIgJiYgY2xlYXJUaW1lb3V0KHRpbWVyKSk7XG4gICAgICAgIGNoaWxkLnN0ZG91dD8ub24oXCJkYXRhXCIsIGNodW5rID0+IHN0ZG91dCArPSBjaHVuayk7XG4gICAgICAgIGNoaWxkLnN0ZGVycj8ub24oXCJkYXRhXCIsIGNodW5rID0+IHN0ZGVyciArPSBjaHVuayk7XG4gICAgICAgIGNoaWxkLm9uKFwiZXJyb3JcIiwgZXJyb3IgPT4gcmVzb2x2ZSh7IHN0ZG91dCwgc3RkZXJyLCBzdGF0dXM6IHVuZGVmaW5lZCwgZXJyb3IgfSkpO1xuICAgICAgICBjaGlsZC5vbihcImNsb3NlXCIsIHN0YXR1cyA9PiByZXNvbHZlKHsgc3Rkb3V0LCBzdGRlcnIsIHN0YXR1czogc3RhdHVzID8/IHVuZGVmaW5lZCwgZXJyb3I6IHVuZGVmaW5lZCB9KSk7XG4gICAgICAgIC8vIEEgY2hpbGQgdGhhdCBleGl0cyBiZWZvcmUgcmVhZGluZyBldmVyeXRoaW5nIHdvdWxkIG90aGVyd2lzZSByYWlzZSBFUElQRS5cbiAgICAgICAgY2hpbGQuc3RkaW4/Lm9uKFwiZXJyb3JcIiwgKCkgPT4gdW5kZWZpbmVkKTtcbiAgICAgICAgY2hpbGQuc3RkaW4/LmVuZChpbnB1dCB8fCBcIlwiKTtcbiAgICB9KTtcbn1cbiJdfQ==
|
|
34
|
-
/* _JS_SOURCE_HASH = "
|
|
34
|
+
/* _JS_SOURCE_HASH = "d2f47a3b98d8645feac1558871c6670685fd9583f40bccb7d60d8f0ced0051ac"; */
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare const SUDO_PREAMBLE = "SUDO=\"\"; if [ \"$(id -u)\" -ne 0 ]; then SUDO=\"sudo -n\"; fi";
|
|
2
|
+
/** No host means this machine, so the very same script runs with nothing in front of it. Local and
|
|
3
|
+
remote then do exactly the same thing, rather than being two pieces of code that have to be kept
|
|
4
|
+
saying the same thing. */
|
|
5
|
+
export declare const THIS_MACHINE = "";
|
|
6
|
+
export declare function describeHost(host: string): string;
|
|
7
|
+
/** The host string is handed to ssh untouched. Users, keys and ports belong in the caller's ssh
|
|
8
|
+
config, so BatchMode makes a missing setup fail immediately instead of prompting. */
|
|
9
|
+
export declare function runOverSSH(config: {
|
|
10
|
+
host: string;
|
|
11
|
+
script: string;
|
|
12
|
+
input?: string;
|
|
13
|
+
allowFailure?: boolean;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
stdout: string;
|
|
16
|
+
stderr: string;
|
|
17
|
+
status: number | undefined;
|
|
18
|
+
}>;
|
|
19
|
+
/** Returns undefined when the file does not exist, so a missing file reads differently from an
|
|
20
|
+
empty one. */
|
|
21
|
+
export declare function readRemoteFile(config: {
|
|
22
|
+
host: string;
|
|
23
|
+
filePath: string;
|
|
24
|
+
}): Promise<string | undefined>;
|
|
25
|
+
export declare function writeRemoteFile(config: {
|
|
26
|
+
host: string;
|
|
27
|
+
filePath: string;
|
|
28
|
+
contents: string;
|
|
29
|
+
fileMode: string;
|
|
30
|
+
directoryMode: string;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
export declare function remoteCommandExists(config: {
|
|
33
|
+
host: string;
|
|
34
|
+
command: string;
|
|
35
|
+
}): Promise<boolean>;
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import crypto from "crypto";
|
|
2
|
-
import
|
|
3
|
-
import { expandHome } from "../helpers/paths";
|
|
4
|
-
import { formatOpenSSHPrivateKey, formatPublicKeyLine, parseOpenSSHPrivateKey, publicKeyFromSeed } from "./sshKeyFile";
|
|
5
|
-
|
|
6
|
-
const USAGE = `Usage: yarn derivekey <label> <source-key> <derived-key>
|
|
7
|
-
|
|
8
|
-
Derives a second ed25519 key from an existing one. The same label and source key always give the
|
|
9
|
-
same derived key, so it can be worked out again rather than backed up.
|
|
10
|
-
|
|
11
|
-
yarn derivekey revokegithubkey ~/authorized_keys_access/id_ed25519 ~/authorized_keys_access/id_ed25519_revoke`;
|
|
2
|
+
import { publicKeyFromSeed } from "./sshKeyFile";
|
|
12
3
|
|
|
13
4
|
/** A label mixed into a key's secret to get another key, so one key can stand behind several
|
|
14
5
|
identities without any of them being stored.
|
|
@@ -24,49 +15,3 @@ export function deriveEd25519Key(config: { seed: Buffer; label: string }) {
|
|
|
24
15
|
let derivedSeed = crypto.createHmac("sha256", seed).update(label, "utf8").digest();
|
|
25
16
|
return { seed: derivedSeed, publicKey: publicKeyFromSeed(derivedSeed) };
|
|
26
17
|
}
|
|
27
|
-
|
|
28
|
-
async function pathExists(filePath: string) {
|
|
29
|
-
try {
|
|
30
|
-
await fs.access(filePath);
|
|
31
|
-
return true;
|
|
32
|
-
} catch (e) {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function parseArgs(argv: string[]) {
|
|
38
|
-
if (argv.length !== 3) {
|
|
39
|
-
throw new Error(`Expected a label, a source key and a derived key, was ${argv.length} argument(s)\n${USAGE}`);
|
|
40
|
-
}
|
|
41
|
-
let [label, sourcePath, derivedPath] = argv;
|
|
42
|
-
return { label, sourcePath: expandHome(sourcePath), derivedPath: expandHome(derivedPath) };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function main() {
|
|
46
|
-
let { label, sourcePath, derivedPath } = parseArgs(process.argv.slice(2));
|
|
47
|
-
|
|
48
|
-
if (!await pathExists(sourcePath)) {
|
|
49
|
-
throw new Error(`Expected a source key at ${sourcePath}, no such file exists`);
|
|
50
|
-
}
|
|
51
|
-
// Writing over a key would strand whatever it already gives access to, and the derived key can
|
|
52
|
-
// always be worked out again, so there is never a reason to overwrite.
|
|
53
|
-
if (await pathExists(derivedPath)) {
|
|
54
|
-
throw new Error(
|
|
55
|
-
`Expected ${derivedPath} to not exist, it does. Deriving the same label again gives the`
|
|
56
|
-
+ ` same key, so delete it first if you want it rewritten.`
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let source = parseOpenSSHPrivateKey(await fs.readFile(sourcePath, "utf8"));
|
|
61
|
-
let derived = deriveEd25519Key({ seed: source.seed, label });
|
|
62
|
-
let comment = `${source.comment || sourcePath} ${label}`.trim();
|
|
63
|
-
|
|
64
|
-
await fs.writeFile(derivedPath, formatOpenSSHPrivateKey({ seed: derived.seed, comment }), { mode: 0o600 });
|
|
65
|
-
let publicLine = formatPublicKeyLine({ publicKey: derived.publicKey, comment });
|
|
66
|
-
await fs.writeFile(`${derivedPath}.pub`, publicLine, { mode: 0o644 });
|
|
67
|
-
|
|
68
|
-
console.log(`Derived "${label}" from ${sourcePath}`);
|
|
69
|
-
console.log(` private ${derivedPath}`);
|
|
70
|
-
console.log(` public ${derivedPath}.pub`);
|
|
71
|
-
console.log(publicLine.trim());
|
|
72
|
-
}
|