sliftutils 1.7.127 → 1.7.129
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 +9 -0
- package/bin/machineclient.js +9 -0
- package/bin/machineserver.js +10 -0
- package/index.d.ts +15 -0
- package/misc/dist/ownIPs.ts.cache +43 -0
- package/misc/dist/strings.ts.cache +2 -2
- package/misc/https/dist/certs.ts.cache +2 -2
- package/misc/https/dist/persistentLocalStorage.ts.cache +2 -2
- package/misc/ownIPs.d.ts +11 -0
- package/misc/ownIPs.ts +34 -0
- package/package.json +9 -3
- package/security/authorizedKeys/authorizedKeys.ts +34 -0
- package/security/authorizedKeys/daemon/authLog.ts +12 -5
- package/security/authorizedKeys/daemon/changes.ts +16 -1
- package/security/authorizedKeys/daemon/daemon.ts +50 -40
- package/security/authorizedKeys/daemon/dist/changes.ts.cache +40 -0
- package/security/authorizedKeys/daemon/dist/git.ts.cache +121 -0
- package/security/authorizedKeys/daemon/dist/notify.ts.cache +39 -0
- package/security/authorizedKeys/daemon/dist/paths.ts.cache +29 -0
- package/security/authorizedKeys/daemon/dist/repoFiles.ts.cache +114 -0
- package/security/authorizedKeys/daemon/dist/revocation.ts.cache +0 -0
- package/security/authorizedKeys/daemon/dist/sessions.ts.cache +147 -0
- package/security/authorizedKeys/daemon/dist/state.ts.cache +74 -0
- package/security/authorizedKeys/daemon/dist/trust.ts.cache +295 -0
- package/security/authorizedKeys/daemon/git.ts +3 -3
- package/security/authorizedKeys/daemon/notify.ts +8 -2
- package/security/authorizedKeys/daemon/repoFiles.ts +118 -0
- package/security/authorizedKeys/daemon/revocation.ts +0 -0
- package/security/authorizedKeys/daemon/rootKeys.ts +77 -10
- package/security/authorizedKeys/daemon/state.ts +25 -7
- package/security/authorizedKeys/daemon/trust.ts +37 -16
- package/security/authorizedKeys/daemon/userKeys.ts +5 -4
- package/security/authorizedKeys/dist/authorizedKeys.ts.cache +37 -3
- package/security/authorizedKeys/dist/revokeSource.ts.cache +17 -4
- package/security/authorizedKeys/dist/sources.ts.cache +52 -8
- package/security/authorizedKeys/dist/unrevoke.ts.cache +113 -17
- package/security/authorizedKeys/revokeSource.ts +12 -2
- package/security/authorizedKeys/secureSSH.ts +66 -12
- package/security/authorizedKeys/sources.ts +47 -5
- package/security/authorizedKeys/unrevoke.ts +176 -16
- package/security/helpers/dist/remoteSSH.ts.cache +19 -9
- package/security/machines/addMachine.ts +83 -0
- package/security/machines/dist/addMachine.ts.cache +88 -0
- package/security/machines/dist/identity.ts.cache +176 -0
- package/security/machines/dist/machines.ts.cache +322 -0
- package/security/machines/dist/trustClient.ts.cache +48 -0
- package/security/machines/dist/trustState.ts.cache +57 -0
- package/security/machines/identity.ts +199 -0
- package/security/machines/machines.ts +355 -0
- package/security/machines/trustClient.ts +51 -0
- package/security/machines/trustServer.ts +85 -0
- package/security/machines/trustState.ts +53 -0
- package/security/notifications/discord.ts +14 -5
- package/security/notifications/dist/discord.ts.cache +17 -8
|
@@ -2,12 +2,14 @@ import fs from "fs/promises";
|
|
|
2
2
|
import os from "os";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { runPromise } from "socket-function/src/runPromise";
|
|
5
|
-
import { keyFingerprint, normalizeKeys } from "./authorizedKeys";
|
|
6
|
-
import { revokeRepoURL } from "./revokeSource";
|
|
5
|
+
import { allowAddresses, keyFingerprint, normalizeKeys, summarizeKey } from "./authorizedKeys";
|
|
6
|
+
import { deriveRevokeKey, revokeRepoURL } from "./revokeSource";
|
|
7
|
+
import { findSourceKey } from "./sources";
|
|
7
8
|
import { signRepo } from "../signedFiles/signFiles";
|
|
8
9
|
import { readRepoKeys } from "./authorizedKeys";
|
|
9
10
|
import { expandHome } from "../helpers/paths";
|
|
10
11
|
import { spawnPromise } from "../helpers/spawn";
|
|
12
|
+
import { machineState } from "../machines/machines";
|
|
11
13
|
|
|
12
14
|
const UNREVOKES_DIR = "unrevoked";
|
|
13
15
|
const REVOCATIONS_DIR = "revocations";
|
|
@@ -22,26 +24,54 @@ passes. It signs the result, and with ${GIT_KEYWORD} it commits and pushes it to
|
|
|
22
24
|
Keys that were revoked should normally be deleted from the repo instead. Unrevoking only matters
|
|
23
25
|
for a key you still want.`;
|
|
24
26
|
|
|
27
|
+
/** One revocation event. Either an ssh key used from an address it is not allowed from, or a
|
|
28
|
+
machine that talked to us from one. Both are the same shape - an identity and an address - and
|
|
29
|
+
an unrevoke undoes either by allowing that same pair.
|
|
30
|
+
|
|
31
|
+
Which is why unrevoking never makes anything unrevokable: used from some other address, it is
|
|
32
|
+
revoked again, under a new id no existing unrevoke names. */
|
|
25
33
|
export type Revocation = {
|
|
26
34
|
revocationId: string;
|
|
27
|
-
fingerprint
|
|
35
|
+
// Exactly one of these. A key revocation names a fingerprint, a machine revocation a machineId.
|
|
36
|
+
fingerprint?: string;
|
|
37
|
+
machineId?: string;
|
|
38
|
+
ip?: string;
|
|
28
39
|
key?: string;
|
|
29
40
|
revokedAt?: string;
|
|
30
41
|
revokedBy?: string;
|
|
42
|
+
reason?: string;
|
|
31
43
|
attempt?: { ip?: string; user?: string; required?: string };
|
|
32
44
|
};
|
|
33
45
|
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
/** Older revocation files carry the address under attempt only. */
|
|
47
|
+
export function revocationIP(revocation: Revocation) {
|
|
48
|
+
return revocation.ip || revocation.attempt?.ip || "";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** What the revocation is about, which is what an unrevoke has to name to undo it. */
|
|
52
|
+
export function revocationIdentity(revocation: Revocation) {
|
|
53
|
+
return revocation.fingerprint || revocation.machineId || "";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Every revocation the revoke repo lists. Cloned read only into a temp directory.
|
|
57
|
+
|
|
58
|
+
A person running this has their own access to the repo, so their credentials are what is used.
|
|
59
|
+
On a machine that is already a portsecure host there may be no such credentials, but the source
|
|
60
|
+
deploy key is right there, and the key derived from it is one that repo definitely accepts. */
|
|
37
61
|
export async function readRemoteRevocations(config: { sourceURL: string }) {
|
|
38
62
|
let { sourceURL } = config;
|
|
39
63
|
let temporaryDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "unrevoke-"));
|
|
40
64
|
let repoPath = path.join(temporaryDirectory, "repo");
|
|
41
|
-
let
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
let cloneArgs = ["clone", "--depth", "1", revokeRepoURL(sourceURL), repoPath];
|
|
66
|
+
let sourceKey = await findSourceKey(sourceURL);
|
|
67
|
+
if (sourceKey) {
|
|
68
|
+
let derived = deriveRevokeKey(await fs.readFile(sourceKey, "utf8"));
|
|
69
|
+
let derivedKeyPath = path.join(temporaryDirectory, "key");
|
|
70
|
+
await fs.writeFile(derivedKeyPath, derived.privateKeyFile, { mode: 0o600 });
|
|
71
|
+
let sshCommand = `ssh -i ${derivedKeyPath} -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new`;
|
|
72
|
+
cloneArgs = ["-c", `core.sshCommand=${sshCommand}`, ...cloneArgs];
|
|
73
|
+
}
|
|
74
|
+
let clone = await spawnPromise({ command: "git", args: cloneArgs });
|
|
45
75
|
if (clone.status !== 0) {
|
|
46
76
|
await fs.rm(temporaryDirectory, { recursive: true, force: true });
|
|
47
77
|
throw new Error(
|
|
@@ -77,6 +107,10 @@ export async function revokedKeysInRepo(config: { repoPath: string; sourceURL: s
|
|
|
77
107
|
continue;
|
|
78
108
|
}
|
|
79
109
|
let parsed = JSON.parse(await fs.readFile(path.join(directory, name), "utf8"));
|
|
110
|
+
for (let allowed of parsed.allowed || []) {
|
|
111
|
+
unrevoked.add(`${allowed.fingerprint || allowed.machineId} ${allowed.ip}`);
|
|
112
|
+
}
|
|
113
|
+
// Named ids, for the unrevokes written before this was about pairs.
|
|
80
114
|
for (let revocationId of parsed.revocationIds || []) {
|
|
81
115
|
unrevoked.add(revocationId);
|
|
82
116
|
}
|
|
@@ -84,13 +118,84 @@ export async function revokedKeysInRepo(config: { repoPath: string; sourceURL: s
|
|
|
84
118
|
} catch (e) {
|
|
85
119
|
// Nothing has been unrevoked.
|
|
86
120
|
}
|
|
87
|
-
let stillRevoked = revocations.filter(revocation => !unrevoked.has(revocation.revocationId)
|
|
121
|
+
let stillRevoked = revocations.filter(revocation => !unrevoked.has(revocation.revocationId)
|
|
122
|
+
&& !unrevoked.has(`${revocation.fingerprint} ${revocationIP(revocation)}`));
|
|
88
123
|
let keys = normalizeKeys(await fs.readFile(path.join(repoPath, "authorized_keys"), "utf8").catch(() => ""));
|
|
89
124
|
return stillRevoked
|
|
90
125
|
.filter(revocation => keys.some(key => keyFingerprint(key) === revocation.fingerprint))
|
|
91
126
|
.map(revocation => ({ revocation, key: keys.find(key => keyFingerprint(key) === revocation.fingerprint) || "" }));
|
|
92
127
|
}
|
|
93
128
|
|
|
129
|
+
/** Writes the addresses into the from= list of each key in the repo's authorized_keys, and says
|
|
130
|
+
which ones were actually new. Only the lines being changed are touched, so comments, ordering
|
|
131
|
+
and any key not involved come out of this byte for byte the same. */
|
|
132
|
+
async function allowAddressesInRepo(config: {
|
|
133
|
+
repoPath: string;
|
|
134
|
+
addressesByFingerprint: Map<string, string[]>;
|
|
135
|
+
}) {
|
|
136
|
+
let { repoPath, addressesByFingerprint } = config;
|
|
137
|
+
let added = new Map<string, string[]>();
|
|
138
|
+
let filePath = path.join(repoPath, "authorized_keys");
|
|
139
|
+
let contents;
|
|
140
|
+
try {
|
|
141
|
+
contents = await fs.readFile(filePath, "utf8");
|
|
142
|
+
} catch (e) {
|
|
143
|
+
// A repo holding its keys as separate .pub files. Nothing to edit in one place, so the
|
|
144
|
+
// caller's listing is all whoever ran this gets.
|
|
145
|
+
console.log(`No authorized_keys in ${repoPath}, so the addresses were not added to any key.`);
|
|
146
|
+
return added;
|
|
147
|
+
}
|
|
148
|
+
let lines = contents.split("\n");
|
|
149
|
+
for (let index = 0; index < lines.length; index++) {
|
|
150
|
+
let fingerprint = keyFingerprint(lines[index]);
|
|
151
|
+
let addresses = fingerprint && addressesByFingerprint.get(fingerprint);
|
|
152
|
+
if (!addresses || !addresses.length) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
let result = allowAddresses(lines[index], addresses);
|
|
156
|
+
lines[index] = result.keyLine;
|
|
157
|
+
added.set(fingerprint, [...(added.get(fingerprint) || []), ...result.added]);
|
|
158
|
+
}
|
|
159
|
+
if ([...added.values()].some(list => list.length)) {
|
|
160
|
+
await fs.writeFile(filePath, lines.join("\n"));
|
|
161
|
+
}
|
|
162
|
+
return added;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Puts each machine's addresses back into its own file, which is the machine equivalent of adding
|
|
166
|
+
an address to a key's from= list: without it the machine is allowed again in principle and
|
|
167
|
+
still rejected on sight, which revokes it once more. */
|
|
168
|
+
async function allowAddressesForMachines(config: { repoPath: string; revocations: Revocation[] }) {
|
|
169
|
+
let { repoPath, revocations } = config;
|
|
170
|
+
let added = new Map<string, string[]>();
|
|
171
|
+
// Read the whole list, change what these revocations are about, write it back. Setting is by
|
|
172
|
+
// the whole set, so writing one machine at a time would delete every other machine.
|
|
173
|
+
let machines = await machineState({ repoPath });
|
|
174
|
+
for (let revocation of revocations) {
|
|
175
|
+
let machineId = revocation.machineId;
|
|
176
|
+
let ip = revocationIP(revocation);
|
|
177
|
+
if (!machineId || !ip) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
let existing = machines.find(machine => machine.machineId === machineId);
|
|
181
|
+
if (!existing) {
|
|
182
|
+
// Removed from the repo since it was revoked, so there is nothing to allow. Deleting a
|
|
183
|
+
// machine is the stronger statement and it stands.
|
|
184
|
+
console.log(`machines/${machineId}.json is not in this repo, so ${ip} was not added to it`);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (existing.ips.includes(ip)) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
existing.ips = [...existing.ips, ip];
|
|
191
|
+
added.set(machineId, [...(added.get(machineId) || []), ip]);
|
|
192
|
+
}
|
|
193
|
+
if (added.size) {
|
|
194
|
+
await machineState({ repoPath, machines });
|
|
195
|
+
}
|
|
196
|
+
return added;
|
|
197
|
+
}
|
|
198
|
+
|
|
94
199
|
/** The repo named, or the one we are standing in. Checked the same way securessh checks it: a git
|
|
95
200
|
repo that actually holds keys and has an origin to clone from. */
|
|
96
201
|
async function resolveKeysRepo(named: string | undefined) {
|
|
@@ -140,22 +245,77 @@ export async function main() {
|
|
|
140
245
|
await fs.writeFile(path.join(directory, `${unrevokeId}.json`), JSON.stringify({
|
|
141
246
|
unrevokeId,
|
|
142
247
|
createdAt: new Date().toISOString(),
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
|
|
248
|
+
// What this file says: each of these keys is allowed from this address. Pairs, not keys,
|
|
249
|
+
// and not a blanket "allow everything again" - a key forgiven here for one address is
|
|
250
|
+
// still revoked the moment it is used from another.
|
|
251
|
+
allowed: revocations.map(revocation => ({
|
|
252
|
+
fingerprint: revocation.fingerprint,
|
|
253
|
+
machineId: revocation.machineId,
|
|
254
|
+
ip: revocationIP(revocation),
|
|
255
|
+
})),
|
|
146
256
|
revocations: revocations.map(revocation => ({
|
|
147
257
|
revocationId: revocation.revocationId,
|
|
148
258
|
fingerprint: revocation.fingerprint,
|
|
259
|
+
machineId: revocation.machineId,
|
|
260
|
+
ip: revocationIP(revocation),
|
|
149
261
|
revokedAt: revocation.revokedAt,
|
|
150
262
|
revokedBy: revocation.revokedBy,
|
|
151
263
|
attempt: revocation.attempt,
|
|
152
264
|
})),
|
|
153
265
|
}, undefined, 4) + "\n");
|
|
154
266
|
|
|
155
|
-
|
|
267
|
+
// Named the way the notifications name them. A fingerprint is not in authorized_keys and
|
|
268
|
+
// cannot be worked out by eye, so on its own it tells whoever is reading this nothing about
|
|
269
|
+
// which of their keys is being talked about.
|
|
270
|
+
let keysByFingerprint = new Map<string, string>();
|
|
271
|
+
for (let key of await readRepoKeys(repoPath).catch(() => [] as string[])) {
|
|
272
|
+
keysByFingerprint.set(keyFingerprint(key), key);
|
|
273
|
+
}
|
|
274
|
+
let describeKey = (revocation: Revocation) => {
|
|
275
|
+
let key = keysByFingerprint.get(revocation.fingerprint || "") || revocation.key || "";
|
|
276
|
+
return key && summarizeKey(key) || `a key no longer in this repo (${revocation.fingerprint})`;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
let addressesByIdentity = new Map<string, string[]>();
|
|
156
280
|
for (let revocation of revocations) {
|
|
157
|
-
|
|
281
|
+
let ip = revocationIP(revocation);
|
|
282
|
+
let identity = revocationIdentity(revocation);
|
|
283
|
+
let existing = addressesByIdentity.get(identity) || [];
|
|
284
|
+
if (ip && !existing.includes(ip)) {
|
|
285
|
+
existing.push(ip);
|
|
286
|
+
}
|
|
287
|
+
addressesByIdentity.set(identity, existing);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Cancelling the revocation on its own would leave sshd refusing that address, and the machines
|
|
291
|
+
// revoking all over again, so the addresses go into the key's from= list and into the machine's
|
|
292
|
+
// own file here. We are in the repo that owns both, so there is nothing to hand back to whoever
|
|
293
|
+
// ran this.
|
|
294
|
+
let added = await allowAddressesInRepo({ repoPath, addressesByFingerprint: addressesByIdentity });
|
|
295
|
+
let addedForMachines = await allowAddressesForMachines({ repoPath, revocations });
|
|
296
|
+
|
|
297
|
+
console.log("");
|
|
298
|
+
for (let [identity, addresses] of addressesByIdentity) {
|
|
299
|
+
let entries = revocations.filter(entry => revocationIdentity(entry) === identity);
|
|
300
|
+
let machineId = entries[0].machineId;
|
|
301
|
+
console.log(`Unfreezing ${machineId && `machine ${machineId}` || describeKey(entries[0])}`);
|
|
302
|
+
console.log(`Allowing IP ${addresses.join(", ") || "(no address recorded)"}`);
|
|
303
|
+
let addedHere = machineId && addedForMachines.get(machineId) || added.get(identity) || [];
|
|
304
|
+
if (addedHere.length) {
|
|
305
|
+
let where = machineId && `machines/${machineId}.json` || `that key's from= list in authorized_keys`;
|
|
306
|
+
console.log(` added to ${where}: ${addedHere.join(", ")}`);
|
|
307
|
+
} else {
|
|
308
|
+
console.log(` already allowed from ${addresses.join(", ") || "everywhere"}`);
|
|
309
|
+
}
|
|
310
|
+
for (let entry of entries) {
|
|
311
|
+
console.log(
|
|
312
|
+
` frozen by ${entry.revokedBy || "?"} ${entry.revokedAt || "at an unrecorded time"}`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
console.log("");
|
|
158
316
|
}
|
|
317
|
+
console.log(`Every other address stays frozen, and any of these used from anywhere else is`);
|
|
318
|
+
console.log(`frozen again.`);
|
|
159
319
|
|
|
160
320
|
// Signed here rather than left as a step to remember. An unrevoke nobody signed does nothing at
|
|
161
321
|
// all, and the repo would sit there looking done while every machine ignored it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
|
|
3
|
-
//exports.remoteCommandExists = exports.writeRemoteFile = exports.readRemoteFile = exports.runOverSSH = exports.SUDO_PREAMBLE = void 0;
|
|
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");
|
|
5
5
|
const SSH_CONNECT_TIMEOUT = 10;
|
|
6
6
|
const EXISTS_MARKER = "__PORTSECURE_EXISTS__";
|
|
@@ -8,27 +8,37 @@ const MISSING_MARKER = "__PORTSECURE_MISSING__";
|
|
|
8
8
|
const MAX_ERROR_BODY_LENGTH = 500;
|
|
9
9
|
// /etc is root owned, so fall back to sudo whenever the SSH user is not root.
|
|
10
10
|
exports.SUDO_PREAMBLE = `SUDO=""; if [ "$(id -u)" -ne 0 ]; then SUDO="sudo -n"; fi`;
|
|
11
|
+
/** No host means this machine, so the very same script runs with nothing in front of it. Local and
|
|
12
|
+
remote then do exactly the same thing, rather than being two pieces of code that have to be kept
|
|
13
|
+
saying the same thing. */
|
|
14
|
+
exports.THIS_MACHINE = "";
|
|
15
|
+
function describeHost(host) {
|
|
16
|
+
return host || "this machine";
|
|
17
|
+
}
|
|
18
|
+
exports.describeHost = describeHost;
|
|
11
19
|
/** The host string is handed to ssh untouched. Users, keys and ports belong in the caller's ssh
|
|
12
20
|
config, so BatchMode makes a missing setup fail immediately instead of prompting. */
|
|
13
21
|
async function runOverSSH(config) {
|
|
14
22
|
let { host, script, input, allowFailure } = config;
|
|
15
23
|
let result = await (0, spawn_1.spawnPromise)({
|
|
16
|
-
command: "ssh",
|
|
17
|
-
args: [
|
|
24
|
+
command: host && "ssh" || "sh",
|
|
25
|
+
args: host && [
|
|
18
26
|
"-o", "BatchMode=yes",
|
|
19
27
|
"-o", `ConnectTimeout=${SSH_CONNECT_TIMEOUT}`,
|
|
20
28
|
host,
|
|
21
29
|
script,
|
|
22
|
-
],
|
|
30
|
+
] || ["-c", script],
|
|
23
31
|
input,
|
|
24
32
|
inheritStderr: !allowFailure,
|
|
25
33
|
});
|
|
26
34
|
if (result.error) {
|
|
27
|
-
throw new Error(`Expected
|
|
35
|
+
throw new Error(`Expected to run against ${describeHost(host)}, failed with ${result.error.message}`);
|
|
28
36
|
}
|
|
29
37
|
if (result.status !== 0 && !allowFailure) {
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
let advice = host
|
|
39
|
+
&& ` Non-interactive ssh access to ${host} has to work on its own - fix it in your ssh config.`
|
|
40
|
+
|| "";
|
|
41
|
+
throw new Error(`Expected the script on ${describeHost(host)} to exit 0, was ${result.status}.${advice}\n`
|
|
32
42
|
+ `${(result.stdout + result.stderr).trim().slice(0, MAX_ERROR_BODY_LENGTH)}`);
|
|
33
43
|
}
|
|
34
44
|
return { stdout: result.stdout || "", stderr: result.stderr || "", status: result.status };
|
|
@@ -86,5 +96,5 @@ async function remoteCommandExists(config) {
|
|
|
86
96
|
return result.stdout.trim() === "yes";
|
|
87
97
|
}
|
|
88
98
|
exports.remoteCommandExists = remoteCommandExists;
|
|
89
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
90
|
-
/* _JS_SOURCE_HASH = "
|
|
99
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVtb3RlU1NILmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsicmVtb3RlU1NILnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLG1DQUF1QztBQUV2QyxNQUFNLG1CQUFtQixHQUFHLEVBQUUsQ0FBQztBQUMvQixNQUFNLGFBQWEsR0FBRyx1QkFBdUIsQ0FBQztBQUM5QyxNQUFNLGNBQWMsR0FBRyx3QkFBd0IsQ0FBQztBQUNoRCxNQUFNLHFCQUFxQixHQUFHLEdBQUcsQ0FBQztBQUVsQyw4RUFBOEU7QUFDakUsUUFBQSxhQUFhLEdBQUcsMkRBQTJELENBQUM7QUFFekY7OzZCQUU2QjtBQUNoQixRQUFBLFlBQVksR0FBRyxFQUFFLENBQUM7QUFFL0IsU0FBZ0IsWUFBWSxDQUFDLElBQVk7SUFDckMsT0FBTyxJQUFJLElBQUksY0FBYyxDQUFDO0FBQ2xDLENBQUM7QUFGRCxvQ0FFQztBQUVEO3dGQUN3RjtBQUNqRixLQUFLLFVBQVUsVUFBVSxDQUFDLE1BQWdGO0lBQzdHLElBQUksRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxZQUFZLEVBQUUsR0FBRyxNQUFNLENBQUM7SUFDbkQsSUFBSSxNQUFNLEdBQUcsTUFBTSxJQUFBLG9CQUFZLEVBQUM7UUFDNUIsT0FBTyxFQUFFLElBQUksSUFBSSxLQUFLLElBQUksSUFBSTtRQUM5QixJQUFJLEVBQUUsSUFBSSxJQUFJO1lBQ1YsSUFBSSxFQUFFLGVBQWU7WUFDckIsSUFBSSxFQUFFLGtCQUFrQixtQkFBbUIsRUFBRTtZQUM3QyxJQUFJO1lBQ0osTUFBTTtTQUNULElBQUksQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDO1FBQ25CLEtBQUs7UUFDTCxhQUFhLEVBQUUsQ0FBQyxZQUFZO0tBQy9CLENBQUMsQ0FBQztJQUNILElBQUksTUFBTSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ2YsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsWUFBWSxDQUFDLElBQUksQ0FBQyxpQkFBaUIs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LEdBQUcsTUFBTSxVQUFVLENBQUM7UUFDMUIsSUFBSTtRQUNKLE1BQU0sRUFBRSxlQUFlLE9BQU8sMkNBQTJDO1FBQ3pFLFlBQVksRUFBRSxJQUFJO0tBQ3JCLENBQUMsQ0FBQztJQUNILE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsS0FBSyxLQUFLLENBQUM7QUFDMUMsQ0FBQztBQVJELGtEQVFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgc3Bhd25Qcm9taXNlIH0gZnJvbSBcIi4vc3Bhd25cIjtcblxuY29uc3QgU1NIX0NPTk5FQ1RfVElNRU9VVCA9IDEwO1xuY29uc3QgRVhJU1RTX01BUktFUiA9IFwiX19QT1JUU0VDVVJFX0VYSVNUU19fXCI7XG5jb25zdCBNSVNTSU5HX01BUktFUiA9IFwiX19QT1JUU0VDVVJFX01JU1NJTkdfX1wiO1xuY29uc3QgTUFYX0VSUk9SX0JPRFlfTEVOR1RIID0gNTAwO1xuXG4vLyAvZXRjIGlzIHJvb3Qgb3duZWQsIHNvIGZhbGwgYmFjayB0byBzdWRvIHdoZW5ldmVyIHRoZSBTU0ggdXNlciBpcyBub3Qgcm9vdC5cbmV4cG9ydCBjb25zdCBTVURPX1BSRUFNQkxFID0gYFNVRE89XCJcIjsgaWYgWyBcIiQoaWQgLXUpXCIgLW5lIDAgXTsgdGhlbiBTVURPPVwic3VkbyAtblwiOyBmaWA7XG5cbi8qKiBObyBob3N0IG1lYW5zIHRoaXMgbWFjaGluZSwgc28gdGhlIHZlcnkgc2FtZSBzY3JpcHQgcnVucyB3aXRoIG5vdGhpbmcgaW4gZnJvbnQgb2YgaXQuIExvY2FsIGFuZFxuICAgIHJlbW90ZSB0aGVuIGRvIGV4YWN0bHkgdGhlIHNhbWUgdGhpbmcsIHJhdGhlciB0aGFuIGJlaW5nIHR3byBwaWVjZXMgb2YgY29kZSB0aGF0IGhhdmUgdG8gYmUga2VwdFxuICAgIHNheWluZyB0aGUgc2FtZSB0aGluZy4gKi9cbmV4cG9ydCBjb25zdCBUSElTX01BQ0hJTkUgPSBcIlwiO1xuXG5leHBvcnQgZnVuY3Rpb24gZGVzY3JpYmVIb3N0KGhvc3Q6IHN0cmluZykge1xuICAgIHJldHVybiBob3N0IHx8IFwidGhpcyBtYWNoaW5lXCI7XG59XG5cbi8qKiBUaGUgaG9zdCBzdHJpbmcgaXMgaGFuZGVkIHRvIHNzaCB1bnRvdWNoZWQuIFVzZXJzLCBrZXlzIGFuZCBwb3J0cyBiZWxvbmcgaW4gdGhlIGNhbGxlcidzIHNzaFxuICAgIGNvbmZpZywgc28gQmF0Y2hNb2RlIG1ha2VzIGEgbWlzc2luZyBzZXR1cCBmYWlsIGltbWVkaWF0ZWx5IGluc3RlYWQgb2YgcHJvbXB0aW5nLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJ1bk92ZXJTU0goY29uZmlnOiB7IGhvc3Q6IHN0cmluZzsgc2NyaXB0OiBzdHJpbmc7IGlucHV0Pzogc3RyaW5nOyBhbGxvd0ZhaWx1cmU/OiBib29sZWFuIH0pIHtcbiAgICBsZXQgeyBob3N0LCBzY3JpcHQsIGlucHV0LCBhbGxvd0ZhaWx1cmUgfSA9IGNvbmZpZztcbiAgICBsZXQgcmVzdWx0ID0gYXdhaXQgc3Bhd25Qcm9taXNlKHtcbiAgICAgICAgY29tbWFuZDogaG9zdCAmJiBcInNzaFwiIHx8IFwic2hcIixcbiAgICAgICAgYXJnczogaG9zdCAmJiBbXG4gICAgICAgICAgICBcIi1vXCIsIFwiQmF0Y2hNb2RlPXllc1wiLFxuICAgICAgICAgICAgXCItb1wiLCBgQ29ubmVjdFRpbWVvdXQ9JHtTU0hfQ09OTkVDVF9USU1FT1VUfWAsXG4gICAgICAgICAgICBob3N0LFxuICAgICAgICAgICAgc2NyaXB0LFxuICAgICAgICBdIHx8IFtcIi1jXCIsIHNjcmlwdF0sXG4gICAgICAgIGlucHV0LFxuICAgICAgICBpbmhlcml0U3RkZXJyOiAhYWxsb3dGYWlsdXJlLFxuICAgIH0pO1xuICAgIGlmIChyZXN1bHQuZXJyb3IpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCB0byBydW4gYWdhaW5zdCAke2Rlc2NyaWJlSG9zdChob3N0KX0sIGZhaWxlZCB3aXRoICR7cmVzdWx0LmVycm9yLm1lc3NhZ2V9YCk7XG4gICAgfVxuICAgIGlmIChyZXN1bHQuc3RhdHVzICE9PSAwICYmICFhbGxvd0ZhaWx1cmUpIHtcbiAgICAgICAgbGV0IGFkdmljZSA9IGhvc3RcbiAgICAgICAgICAgICYmIGAgTm9uLWludGVyYWN0aXZlIHNzaCBhY2Nlc3MgdG8gJHtob3N0fSBoYXMgdG8gd29yayBvbiBpdHMgb3duIC0gZml4IGl0IGluIHlvdXIgc3NoIGNvbmZpZy5gXG4gICAgICAgICAgICB8fCBcIlwiO1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBgRXhwZWN0ZWQgdGhlIHNjcmlwdCBvbiAke2Rlc2NyaWJlSG9zdChob3N0KX0gdG8gZXhpdCAwLCB3YXMgJHtyZXN1bHQuc3RhdHVzfS4ke2FkdmljZX1cXG5gXG4gICAgICAgICAgICArIGAkeyhyZXN1bHQuc3Rkb3V0ICsgcmVzdWx0LnN0ZGVycikudHJpbSgpLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCl9YFxuICAgICAgICApO1xuICAgIH1cbiAgICByZXR1cm4geyBzdGRvdXQ6IHJlc3VsdC5zdGRvdXQgfHwgXCJcIiwgc3RkZXJyOiByZXN1bHQuc3RkZXJyIHx8IFwiXCIsIHN0YXR1czogcmVzdWx0LnN0YXR1cyB9O1xufVxuXG4vKiogUmV0dXJucyB1bmRlZmluZWQgd2hlbiB0aGUgZmlsZSBkb2VzIG5vdCBleGlzdCwgc28gYSBtaXNzaW5nIGZpbGUgcmVhZHMgZGlmZmVyZW50bHkgZnJvbSBhblxuICAgIGVtcHR5IG9uZS4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByZWFkUmVtb3RlRmlsZShjb25maWc6IHsgaG9zdDogc3RyaW5nOyBmaWxlUGF0aDogc3RyaW5nIH0pIHtcbiAgICBsZXQgeyBob3N0LCBmaWxlUGF0aCB9ID0gY29uZmlnO1xuICAgIGxldCBvdXRwdXQgPSAoYXdhaXQgcnVuT3ZlclNTSCh7XG4gICAgICAgIGhvc3QsXG4gICAgICAgIHNjcmlwdDogYCR7U1VET19QUkVBTUJMRX1cbmlmICRTVURPIHRlc3QgLWYgXCIke2ZpbGVQYXRofVwiOyB0aGVuXG4gICAgZWNobyBcIiR7RVhJU1RTX01BUktFUn1cIlxuICAgICRTVURPIGNhdCBcIiR7ZmlsZVBhdGh9XCJcbmVsc2VcbiAgICBlY2hvIFwiJHtNSVNTSU5HX01BUktFUn1cIlxuZmlgLFxuICAgIH0pKS5zdGRvdXQ7XG4gICAgbGV0IG5ld2xpbmVJbmRleCA9IG91dHB1dC5pbmRleE9mKFwiXFxuXCIpO1xuICAgIGxldCBtYXJrZXIgPSBvdXRwdXQuc2xpY2UoMCwgbmV3bGluZUluZGV4KS50cmltKCk7XG4gICAgaWYgKG1hcmtlciA9PT0gTUlTU0lOR19NQVJLRVIpIHtcbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG4gICAgaWYgKG1hcmtlciAhPT0gRVhJU1RTX01BUktFUikge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkICR7RVhJU1RTX01BUktFUn0gb3IgJHtNSVNTSU5HX01BUktFUn0gZnJvbSAke2hvc3R9LCB3YXMgJHtvdXRwdXQuc2xpY2UoMCwgTUFYX0VSUk9SX0JPRFlfTEVOR1RIKX1gKTtcbiAgICB9XG4gICAgcmV0dXJuIG91dHB1dC5zbGljZShuZXdsaW5lSW5kZXggKyAxKTtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHdyaXRlUmVtb3RlRmlsZShjb25maWc6IHtcbiAgICBob3N0OiBzdHJpbmc7XG4gICAgZmlsZVBhdGg6IHN0cmluZztcbiAgICBjb250ZW50czogc3RyaW5nO1xuICAgIGZpbGVNb2RlOiBzdHJpbmc7XG4gICAgZGlyZWN0b3J5TW9kZTogc3RyaW5nO1xufSkge1xuICAgIGxldCB7IGhvc3QsIGZpbGVQYXRoLCBjb250ZW50cywgZmlsZU1vZGUsIGRpcmVjdG9yeU1vZGUgfSA9IGNvbmZpZztcbiAgICBsZXQgZGlyZWN0b3J5ID0gZmlsZVBhdGguc2xpY2UoMCwgZmlsZVBhdGgubGFzdEluZGV4T2YoXCIvXCIpKTtcbiAgICBhd2FpdCBydW5PdmVyU1NIKHtcbiAgICAgICAgaG9zdCxcbiAgICAgICAgLy8gRGVwbG95aW5nIGZyb20gV2luZG93cyBtdXN0IG5vdCBjYXJyeSBDUkxGIG9udG8gdGhlIHRhcmdldCwgd2hlcmUgaXQgYnJlYWtzIHN5c3RlbWRcbiAgICAgICAgLy8gdW5pdHMgYW5kIG1ha2VzIHByaXZhdGUga2V5cyB1bnJlYWRhYmxlIHRvIHNzaC5cbiAgICAgICAgaW5wdXQ6IGNvbnRlbnRzLnJlcGxhY2UoL1xcclxcbi9nLCBcIlxcblwiKSxcbiAgICAgICAgc2NyaXB0OiBgJHtTVURPX1BSRUFNQkxFfVxuc2V0IC1lXG4kU1VETyBta2RpciAtcCBcIiR7ZGlyZWN0b3J5fVwiXG4kU1VETyBjaG1vZCAke2RpcmVjdG9yeU1vZGV9IFwiJHtkaXJlY3Rvcnl9XCJcbiRTVURPIHRlZSBcIiR7ZmlsZVBhdGh9XCIgPiAvZGV2L251bGxcbiRTVURPIGNobW9kICR7ZmlsZU1vZGV9IFwiJHtmaWxlUGF0aH1cImAsXG4gICAgfSk7XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByZW1vdGVDb21tYW5kRXhpc3RzKGNvbmZpZzogeyBob3N0OiBzdHJpbmc7IGNvbW1hbmQ6IHN0cmluZyB9KSB7XG4gICAgbGV0IHsgaG9zdCwgY29tbWFuZCB9ID0gY29uZmlnO1xuICAgIGxldCByZXN1bHQgPSBhd2FpdCBydW5PdmVyU1NIKHtcbiAgICAgICAgaG9zdCxcbiAgICAgICAgc2NyaXB0OiBgY29tbWFuZCAtdiBcIiR7Y29tbWFuZH1cIiA+IC9kZXYvbnVsbCAyPiYxICYmIGVjaG8geWVzIHx8IGVjaG8gbm9gLFxuICAgICAgICBhbGxvd0ZhaWx1cmU6IHRydWUsXG4gICAgfSk7XG4gICAgcmV0dXJuIHJlc3VsdC5zdGRvdXQudHJpbSgpID09PSBcInllc1wiO1xufVxuIl19
|
|
100
|
+
/* _JS_SOURCE_HASH = "50b1a08f2aa4b96f04f676f20c516bc6c2897a47ad7c12729c0f651735c71057"; */
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import os from "os";
|
|
2
|
+
import { getOwnIPs } from "../../misc/ownIPs";
|
|
3
|
+
import { describeHost } from "../helpers/remoteSSH";
|
|
4
|
+
import { createRemoteIdentity, Identity, localDomains, localIdentity, remoteIdentity } from "./identity";
|
|
5
|
+
import { machineState, resolveKeysRepo } from "./machines";
|
|
6
|
+
|
|
7
|
+
const USAGE = `Usage: yarn addmachine <domain> [ip]
|
|
8
|
+
|
|
9
|
+
The domain is required: a machine has a different identity, and so a different machine id, under
|
|
10
|
+
every domain it runs under, so there is no such thing as "this machine" without one.
|
|
11
|
+
|
|
12
|
+
With no ip it adds this machine, with its own addresses. With an ip it asks that machine over ssh
|
|
13
|
+
for its identity under that domain, gives it one if it has none, and adds it under that address.
|
|
14
|
+
|
|
15
|
+
It edits the keys repo this machine reads, wherever that is, not whatever directory this was run
|
|
16
|
+
from. Nothing is signed or pushed here. Run "yarn signfiles git" in that repo afterwards, or no
|
|
17
|
+
machine will believe any of it.`;
|
|
18
|
+
|
|
19
|
+
export async function main() {
|
|
20
|
+
let argv = process.argv.slice(2);
|
|
21
|
+
if (!argv.length || argv.length > 2) {
|
|
22
|
+
throw new Error(`Expected a domain and optionally an ip, was ${argv.length} argument(s)\n${USAGE}`);
|
|
23
|
+
}
|
|
24
|
+
let [domain, host = ""] = argv;
|
|
25
|
+
// The same repo the acceptance check reads. Editing anything else would write a machine list
|
|
26
|
+
// nothing on this machine ever looks at.
|
|
27
|
+
let { repoPath } = await resolveKeysRepo();
|
|
28
|
+
|
|
29
|
+
let identity: Identity;
|
|
30
|
+
let addresses: string[];
|
|
31
|
+
if (!host) {
|
|
32
|
+
let found = await localIdentity(domain);
|
|
33
|
+
if (!found) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Expected an identity for ${domain} in ${os.homedir()}, this machine has none.\n`
|
|
36
|
+
+ `It has: ${(await localDomains()).join(", ") || "no identities at all"}\n`
|
|
37
|
+
+ `One is generated the first time something runs here under a domain, so run that`
|
|
38
|
+
+ ` first, or name another machine to add instead.`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
identity = found;
|
|
42
|
+
addresses = await getOwnIPs();
|
|
43
|
+
if (!addresses.length) {
|
|
44
|
+
throw new Error(`Expected this machine to have an address anything else can see, it has none`);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
// Nothing there under this domain means it gets one. Its own from this point on: the key
|
|
48
|
+
// is written there and nothing keeps a copy.
|
|
49
|
+
let found = await remoteIdentity(host, domain);
|
|
50
|
+
if (!found) {
|
|
51
|
+
console.log(`${describeHost(host)} has no identity for ${domain}, generating one`);
|
|
52
|
+
}
|
|
53
|
+
identity = found || await createRemoteIdentity({ host, domain });
|
|
54
|
+
// The address it was named by is the address it talks to us from, which is the one thing
|
|
55
|
+
// it cannot lie about.
|
|
56
|
+
addresses = [host];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The whole list is read and written back, because setting is by the set: writing this one
|
|
60
|
+
// machine alone would remove every other machine from the repo.
|
|
61
|
+
let machines = await machineState({ repoPath });
|
|
62
|
+
let existing = machines.find(machine => machine.machineId === identity.machineId);
|
|
63
|
+
let ips = [...(existing?.ips || [])];
|
|
64
|
+
let added = addresses.filter(address => !ips.includes(address));
|
|
65
|
+
if (existing) {
|
|
66
|
+
existing.ips = [...ips, ...added];
|
|
67
|
+
} else {
|
|
68
|
+
machines.push({ machineId: identity.machineId, ips: [...added], addedAt: "" });
|
|
69
|
+
}
|
|
70
|
+
await machineState({ repoPath, machines });
|
|
71
|
+
|
|
72
|
+
console.log(`\nTrusting machine ${identity.machineId}`);
|
|
73
|
+
console.log(`Allowing IP ${[...ips, ...added].join(", ")}`);
|
|
74
|
+
if (added.length) {
|
|
75
|
+
console.log(` added to machines/${identity.machineId}.json: ${added.join(", ")}`);
|
|
76
|
+
} else {
|
|
77
|
+
console.log(` already allowed from ${addresses.join(", ")}`);
|
|
78
|
+
}
|
|
79
|
+
console.log(` identity ${identity.fullDomain}`);
|
|
80
|
+
console.log(` in ${repoPath}`);
|
|
81
|
+
console.log(`\nNothing believes this until it is signed. In that repo, run:`);
|
|
82
|
+
console.log(`\`\`\`\nyarn signfiles git\n\`\`\``);
|
|
83
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule && mod.default) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
|
|
6
|
+
//exports.main = void 0;
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const ownIPs_1 = require("../../misc/ownIPs");
|
|
9
|
+
const remoteSSH_1 = require("../helpers/remoteSSH");
|
|
10
|
+
const identity_1 = require("./identity");
|
|
11
|
+
const machines_1 = require("./machines");
|
|
12
|
+
const USAGE = `Usage: yarn addmachine <domain> [ip]
|
|
13
|
+
|
|
14
|
+
The domain is required: a machine has a different identity, and so a different machine id, under
|
|
15
|
+
every domain it runs under, so there is no such thing as "this machine" without one.
|
|
16
|
+
|
|
17
|
+
With no ip it adds this machine, with its own addresses. With an ip it asks that machine over ssh
|
|
18
|
+
for its identity under that domain, gives it one if it has none, and adds it under that address.
|
|
19
|
+
|
|
20
|
+
It edits the keys repo this machine reads, wherever that is, not whatever directory this was run
|
|
21
|
+
from. Nothing is signed or pushed here. Run "yarn signfiles git" in that repo afterwards, or no
|
|
22
|
+
machine will believe any of it.`;
|
|
23
|
+
async function main() {
|
|
24
|
+
let argv = process.argv.slice(2);
|
|
25
|
+
if (!argv.length || argv.length > 2) {
|
|
26
|
+
throw new Error(`Expected a domain and optionally an ip, was ${argv.length} argument(s)\n${USAGE}`);
|
|
27
|
+
}
|
|
28
|
+
let [domain, host = ""] = argv;
|
|
29
|
+
// The same repo the acceptance check reads. Editing anything else would write a machine list
|
|
30
|
+
// nothing on this machine ever looks at.
|
|
31
|
+
let { repoPath } = await (0, machines_1.resolveKeysRepo)();
|
|
32
|
+
let identity;
|
|
33
|
+
let addresses;
|
|
34
|
+
if (!host) {
|
|
35
|
+
let found = await (0, identity_1.localIdentity)(domain);
|
|
36
|
+
if (!found) {
|
|
37
|
+
throw new Error(`Expected an identity for ${domain} in ${os_1.default.homedir()}, this machine has none.\n`
|
|
38
|
+
+ `It has: ${(await (0, identity_1.localDomains)()).join(", ") || "no identities at all"}\n`
|
|
39
|
+
+ `One is generated the first time something runs here under a domain, so run that`
|
|
40
|
+
+ ` first, or name another machine to add instead.`);
|
|
41
|
+
}
|
|
42
|
+
identity = found;
|
|
43
|
+
addresses = await (0, ownIPs_1.getOwnIPs)();
|
|
44
|
+
if (!addresses.length) {
|
|
45
|
+
throw new Error(`Expected this machine to have an address anything else can see, it has none`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Nothing there under this domain means it gets one. Its own from this point on: the key
|
|
50
|
+
// is written there and nothing keeps a copy.
|
|
51
|
+
let found = await (0, identity_1.remoteIdentity)(host, domain);
|
|
52
|
+
if (!found) {
|
|
53
|
+
console.log(`${(0, remoteSSH_1.describeHost)(host)} has no identity for ${domain}, generating one`);
|
|
54
|
+
}
|
|
55
|
+
identity = found || await (0, identity_1.createRemoteIdentity)({ host, domain });
|
|
56
|
+
// The address it was named by is the address it talks to us from, which is the one thing
|
|
57
|
+
// it cannot lie about.
|
|
58
|
+
addresses = [host];
|
|
59
|
+
}
|
|
60
|
+
// The whole list is read and written back, because setting is by the set: writing this one
|
|
61
|
+
// machine alone would remove every other machine from the repo.
|
|
62
|
+
let machines = await (0, machines_1.machineState)({ repoPath });
|
|
63
|
+
let existing = machines.find(machine => machine.machineId === identity.machineId);
|
|
64
|
+
let ips = [...((existing === null || existing === void 0 ? void 0 : existing.ips) || [])];
|
|
65
|
+
let added = addresses.filter(address => !ips.includes(address));
|
|
66
|
+
if (existing) {
|
|
67
|
+
existing.ips = [...ips, ...added];
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
machines.push({ machineId: identity.machineId, ips: [...added], addedAt: "" });
|
|
71
|
+
}
|
|
72
|
+
await (0, machines_1.machineState)({ repoPath, machines });
|
|
73
|
+
console.log(`\nTrusting machine ${identity.machineId}`);
|
|
74
|
+
console.log(`Allowing IP ${[...ips, ...added].join(", ")}`);
|
|
75
|
+
if (added.length) {
|
|
76
|
+
console.log(` added to machines/${identity.machineId}.json: ${added.join(", ")}`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.log(` already allowed from ${addresses.join(", ")}`);
|
|
80
|
+
}
|
|
81
|
+
console.log(` identity ${identity.fullDomain}`);
|
|
82
|
+
console.log(` in ${repoPath}`);
|
|
83
|
+
console.log(`\nNothing believes this until it is signed. In that repo, run:`);
|
|
84
|
+
console.log(`\`\`\`\nyarn signfiles git\n\`\`\``);
|
|
85
|
+
}
|
|
86
|
+
exports.main = main;
|
|
87
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRkTWFjaGluZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImFkZE1hY2hpbmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsNENBQW9CO0FBQ3BCLDhDQUE4QztBQUM5QyxvREFBb0Q7QUFDcEQseUNBQXlHO0FBQ3pHLHlDQUEyRDtBQUUzRCxNQUFNLEtBQUssR0FBRzs7Ozs7Ozs7OztnQ0FVa0IsQ0FBQztBQUUxQixLQUFLLFVBQVUsSUFBSTtJQUN0QixJQUFJLElBQUksR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNqQyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sSUFBSSxJQUFJLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQ2xDLE1BQU0sSUFBSSxLQUFLLENBQUMsK0NBQStDLElBQUksQ0FBQyxNQUFNLGlCQUFpQixLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBQ3hHLENBQUM7SUFDRCxJQUFJLENBQUMsTUFBTSxFQUFFLElBQUksR0FBRyxFQUFFLENBQUMsR0FBRyxJQUFJLENBQUM7SUFDL0IsNkZBQTZGO0lBQzdGLHlDQUF5QztJQUN6QyxJQUFJLEVBQUUsUUFBUSxFQUFFLEdBQUcsTUFBTSxJQUFBLDBCQUFlLEdBQUUsQ0FBQztJQUUzQyxJQUFJLFFBQWtCLENBQUM7SUFDdkIsSUFBSSxTQUFtQixDQUFDO0lBQ3hCLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUNSLElBQUksS0FBSyxHQUFHLE1BQU0sSUFBQSx3QkFBYSxFQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ3hDLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNULE1BQU0sSUFBSSxLQUFLLENBQ1gsNEJBQTRCLE1BQU0sT0FBTyxZQUFFLENBQUMsT0FBTyxFQUFFLDRCQUE0QjtrQkFDL0UsV0FBVyxDQUFDLE1BQU0sSUFBQSx1QkFBWSxHQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksc0JBQXNCLElBQUk7a0JBQzFFLGlGQUFpRjtrQkFDakYsaURBQWlELENBQ3RELENBQUM7UUFDTixDQUFDO1FBQ0QsUUFBUSxHQUFHLEtBQUssQ0FBQztRQUNqQixTQUFTLEdBQUcsTUFBTSxJQUFBLGtCQUFTLEdBQUUsQ0FBQztRQUM5QixJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ3BCLE1BQU0sSUFBSSxLQUFLLENBQUMsNkVBQTZFLENBQUMsQ0FBQztRQUNuRyxDQUFDO0lBQ0wsQ0FBQztTQUFNLENBQUM7UUFDSix5RkFBeUY7UUFDekYsNkNBQTZDO1FBQzdDLElBQUksS0FBSyxHQUFHLE1BQU0sSUFBQSx5QkFBYyxFQUFDLElBQUksRUFBRSxNQUFNLENBQUMsQ0FBQztRQUMvQyxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7WUFDVCxPQUFPLENBQUMsR0FBRyxDQUFDLEdBQUcsSUFBQSx3QkFBWSxFQUFDLElBQUksQ0FBQyx3QkFBd0IsTUFBTSxrQkFBa0IsQ0FBQyxDQUFDO1FBQ3ZGLENBQUM7UUFDRCxRQUFRLEdBQUcsS0FBSyxJQUFJLE1BQU0sSUFBQSwrQkFBb0IsRUFBQyxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDO1FBQ2pFLHlGQUF5RjtRQUN6Rix1QkFBdUI7UUFDdkIsU0FBUyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDdkIsQ0FBQztJQUVELDJGQUEyRjtJQUMzRixnRUFBZ0U7SUFDaEUsSUFBSSxRQUFRLEdBQUcsTUFBTSxJQUFBLHVCQUFZLEVBQUMsRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFDO0lBQ2hELElBQUksUUFBUSxHQUFHLFFBQVEsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxPQUFPLENBQUMsU0FBUyxLQUFLLFFBQVEsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUNsRixJQUFJLEdBQUcsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFBLFFBQVEsYUFBUixRQUFRLHVCQUFSLFFBQVEsQ0FBRSxHQUFHLEtBQUksRUFBRSxDQUFDLENBQUMsQ0FBQztJQUNyQyxJQUFJLEtBQUssR0FBRyxTQUFTLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7SUFDaEUsSUFBSSxRQUFRLEVBQUUsQ0FBQztRQUNYLFFBQVEsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxHQUFHLEdBQUcsRUFBRSxHQUFHLEtBQUssQ0FBQyxDQUFDO0lBQ3RDLENBQUM7U0FBTSxDQUFDO1FBQ0osUUFBUSxDQUFDLElBQUksQ0FBQyxFQUFFLFNBQVMsRUFBRSxRQUFRLENBQUMsU0FBUyxFQUFFLEdBQUcsRUFBRSxDQUFDLEdBQUcsS0FBSyxDQUFDLEVBQUUsT0FBTyxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDbkYsQ0FBQztJQUNELE1BQU0sSUFBQSx1QkFBWSxFQUFDLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7SUFFM0MsT0FBTyxDQUFDLEdBQUcsQ0FBQyxzQkFBc0IsUUFBUSxDQUFDLFNBQVMsRUFBRSxDQUFDLENBQUM7SUFDeEQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxlQUFlLENBQUMsR0FBRyxHQUFHLEVBQUUsR0FBRyxLQUFLLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQzVELElBQUksS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ2YsT0FBTyxDQUFDLEdBQUcsQ0FBQyx1QkFBdUIsUUFBUSxDQUFDLFNBQVMsVUFBVSxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUN2RixDQUFDO1NBQU0sQ0FBQztRQUNKLE9BQU8sQ0FBQyxHQUFHLENBQUMsMEJBQTBCLFNBQVMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ2xFLENBQUM7SUFDRCxPQUFPLENBQUMsR0FBRyxDQUFDLGNBQWMsUUFBUSxDQUFDLFVBQVUsRUFBRSxDQUFDLENBQUM7SUFDakQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLFFBQVEsRUFBRSxDQUFDLENBQUM7SUFDaEMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnRUFBZ0UsQ0FBQyxDQUFDO0lBQzlFLE9BQU8sQ0FBQyxHQUFHLENBQUMsb0NBQW9DLENBQUMsQ0FBQztBQUN0RCxDQUFDO0FBaEVELG9CQWdFQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBvcyBmcm9tIFwib3NcIjtcbmltcG9ydCB7IGdldE93bklQcyB9IGZyb20gXCIuLi8uLi9taXNjL293bklQc1wiO1xuaW1wb3J0IHsgZGVzY3JpYmVIb3N0IH0gZnJvbSBcIi4uL2hlbHBlcnMvcmVtb3RlU1NIXCI7XG5pbXBvcnQgeyBjcmVhdGVSZW1vdGVJZGVudGl0eSwgSWRlbnRpdHksIGxvY2FsRG9tYWlucywgbG9jYWxJZGVudGl0eSwgcmVtb3RlSWRlbnRpdHkgfSBmcm9tIFwiLi9pZGVudGl0eVwiO1xuaW1wb3J0IHsgbWFjaGluZVN0YXRlLCByZXNvbHZlS2V5c1JlcG8gfSBmcm9tIFwiLi9tYWNoaW5lc1wiO1xuXG5jb25zdCBVU0FHRSA9IGBVc2FnZTogeWFybiBhZGRtYWNoaW5lIDxkb21haW4+IFtpcF1cblxuVGhlIGRvbWFpbiBpcyByZXF1aXJlZDogYSBtYWNoaW5lIGhhcyBhIGRpZmZlcmVudCBpZGVudGl0eSwgYW5kIHNvIGEgZGlmZmVyZW50IG1hY2hpbmUgaWQsIHVuZGVyXG5ldmVyeSBkb21haW4gaXQgcnVucyB1bmRlciwgc28gdGhlcmUgaXMgbm8gc3VjaCB0aGluZyBhcyBcInRoaXMgbWFjaGluZVwiIHdpdGhvdXQgb25lLlxuXG5XaXRoIG5vIGlwIGl0IGFkZHMgdGhpcyBtYWNoaW5lLCB3aXRoIGl0cyBvd24gYWRkcmVzc2VzLiBXaXRoIGFuIGlwIGl0IGFza3MgdGhhdCBtYWNoaW5lIG92ZXIgc3NoXG5mb3IgaXRzIGlkZW50aXR5IHVuZGVyIHRoYXQgZG9tYWluLCBnaXZlcyBpdCBvbmUgaWYgaXQgaGFzIG5vbmUsIGFuZCBhZGRzIGl0IHVuZGVyIHRoYXQgYWRkcmVzcy5cblxuSXQgZWRpdHMgdGhlIGtleXMgcmVwbyB0aGlzIG1hY2hpbmUgcmVhZHMsIHdoZXJldmVyIHRoYXQgaXMsIG5vdCB3aGF0ZXZlciBkaXJlY3RvcnkgdGhpcyB3YXMgcnVuXG5mcm9tLiBOb3RoaW5nIGlzIHNpZ25lZCBvciBwdXNoZWQgaGVyZS4gUnVuIFwieWFybiBzaWduZmlsZXMgZ2l0XCIgaW4gdGhhdCByZXBvIGFmdGVyd2FyZHMsIG9yIG5vXG5tYWNoaW5lIHdpbGwgYmVsaWV2ZSBhbnkgb2YgaXQuYDtcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIG1haW4oKSB7XG4gICAgbGV0IGFyZ3YgPSBwcm9jZXNzLmFyZ3Yuc2xpY2UoMik7XG4gICAgaWYgKCFhcmd2Lmxlbmd0aCB8fCBhcmd2Lmxlbmd0aCA+IDIpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBhIGRvbWFpbiBhbmQgb3B0aW9uYWxseSBhbiBpcCwgd2FzICR7YXJndi5sZW5ndGh9IGFyZ3VtZW50KHMpXFxuJHtVU0FHRX1gKTtcbiAgICB9XG4gICAgbGV0IFtkb21haW4sIGhvc3QgPSBcIlwiXSA9IGFyZ3Y7XG4gICAgLy8gVGhlIHNhbWUgcmVwbyB0aGUgYWNjZXB0YW5jZSBjaGVjayByZWFkcy4gRWRpdGluZyBhbnl0aGluZyBlbHNlIHdvdWxkIHdyaXRlIGEgbWFjaGluZSBsaXN0XG4gICAgLy8gbm90aGluZyBvbiB0aGlzIG1hY2hpbmUgZXZlciBsb29rcyBhdC5cbiAgICBsZXQgeyByZXBvUGF0aCB9ID0gYXdhaXQgcmVzb2x2ZUtleXNSZXBvKCk7XG5cbiAgICBsZXQgaWRlbnRpdHk6IElkZW50aXR5O1xuICAgIGxldCBhZGRyZXNzZXM6IHN0cmluZ1tdO1xuICAgIGlmICghaG9zdCkge1xuICAgICAgICBsZXQgZm91bmQgPSBhd2FpdCBsb2NhbElkZW50aXR5KGRvbWFpbik7XG4gICAgICAgIGlmICghZm91bmQpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBgRXhwZWN0ZWQgYW4gaWRlbnRpdHkgZm9yICR7ZG9tYWlufSBpbiAke29zLmhvbWVkaXIoKX0sIHRoaXMgbWFjaGluZSBoYXMgbm9uZS5cXG5gXG4gICAgICAgICAgICAgICAgKyBgSXQgaGFzOiAkeyhhd2FpdCBsb2NhbERvbWFpbnMoKSkuam9pbihcIiwgXCIpIHx8IFwibm8gaWRlbnRpdGllcyBhdCBhbGxcIn1cXG5gXG4gICAgICAgICAgICAgICAgKyBgT25lIGlzIGdlbmVyYXRlZCB0aGUgZmlyc3QgdGltZSBzb21ldGhpbmcgcnVucyBoZXJlIHVuZGVyIGEgZG9tYWluLCBzbyBydW4gdGhhdGBcbiAgICAgICAgICAgICAgICArIGAgZmlyc3QsIG9yIG5hbWUgYW5vdGhlciBtYWNoaW5lIHRvIGFkZCBpbnN0ZWFkLmBcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgICAgaWRlbnRpdHkgPSBmb3VuZDtcbiAgICAgICAgYWRkcmVzc2VzID0gYXdhaXQgZ2V0T3duSVBzKCk7XG4gICAgICAgIGlmICghYWRkcmVzc2VzLmxlbmd0aCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCB0aGlzIG1hY2hpbmUgdG8gaGF2ZSBhbiBhZGRyZXNzIGFueXRoaW5nIGVsc2UgY2FuIHNlZSwgaXQgaGFzIG5vbmVgKTtcbiAgICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIE5vdGhpbmcgdGhlcmUgdW5kZXIgdGhpcyBkb21haW4gbWVhbnMgaXQgZ2V0cyBvbmUuIEl0cyBvd24gZnJvbSB0aGlzIHBvaW50IG9uOiB0aGUga2V5XG4gICAgICAgIC8vIGlzIHdyaXR0ZW4gdGhlcmUgYW5kIG5vdGhpbmcga2VlcHMgYSBjb3B5LlxuICAgICAgICBsZXQgZm91bmQgPSBhd2FpdCByZW1vdGVJZGVudGl0eShob3N0LCBkb21haW4pO1xuICAgICAgICBpZiAoIWZvdW5kKSB7XG4gICAgICAgICAgICBjb25zb2xlLmxvZyhgJHtkZXNjcmliZUhvc3QoaG9zdCl9IGhhcyBubyBpZGVudGl0eSBmb3IgJHtkb21haW59LCBnZW5lcmF0aW5nIG9uZWApO1xuICAgICAgICB9XG4gICAgICAgIGlkZW50aXR5ID0gZm91bmQgfHwgYXdhaXQgY3JlYXRlUmVtb3RlSWRlbnRpdHkoeyBob3N0LCBkb21haW4gfSk7XG4gICAgICAgIC8vIFRoZSBhZGRyZXNzIGl0IHdhcyBuYW1lZCBieSBpcyB0aGUgYWRkcmVzcyBpdCB0YWxrcyB0byB1cyBmcm9tLCB3aGljaCBpcyB0aGUgb25lIHRoaW5nXG4gICAgICAgIC8vIGl0IGNhbm5vdCBsaWUgYWJvdXQuXG4gICAgICAgIGFkZHJlc3NlcyA9IFtob3N0XTtcbiAgICB9XG5cbiAgICAvLyBUaGUgd2hvbGUgbGlzdCBpcyByZWFkIGFuZCB3cml0dGVuIGJhY2ssIGJlY2F1c2Ugc2V0dGluZyBpcyBieSB0aGUgc2V0OiB3cml0aW5nIHRoaXMgb25lXG4gICAgLy8gbWFjaGluZSBhbG9uZSB3b3VsZCByZW1vdmUgZXZlcnkgb3RoZXIgbWFjaGluZSBmcm9tIHRoZSByZXBvLlxuICAgIGxldCBtYWNoaW5lcyA9IGF3YWl0IG1hY2hpbmVTdGF0ZSh7IHJlcG9QYXRoIH0pO1xuICAgIGxldCBleGlzdGluZyA9IG1hY2hpbmVzLmZpbmQobWFjaGluZSA9PiBtYWNoaW5lLm1hY2hpbmVJZCA9PT0gaWRlbnRpdHkubWFjaGluZUlkKTtcbiAgICBsZXQgaXBzID0gWy4uLihleGlzdGluZz8uaXBzIHx8IFtdKV07XG4gICAgbGV0IGFkZGVkID0gYWRkcmVzc2VzLmZpbHRlcihhZGRyZXNzID0+ICFpcHMuaW5jbHVkZXMoYWRkcmVzcykpO1xuICAgIGlmIChleGlzdGluZykge1xuICAgICAgICBleGlzdGluZy5pcHMgPSBbLi4uaXBzLCAuLi5hZGRlZF07XG4gICAgfSBlbHNlIHtcbiAgICAgICAgbWFjaGluZXMucHVzaCh7IG1hY2hpbmVJZDogaWRlbnRpdHkubWFjaGluZUlkLCBpcHM6IFsuLi5hZGRlZF0sIGFkZGVkQXQ6IFwiXCIgfSk7XG4gICAgfVxuICAgIGF3YWl0IG1hY2hpbmVTdGF0ZSh7IHJlcG9QYXRoLCBtYWNoaW5lcyB9KTtcblxuICAgIGNvbnNvbGUubG9nKGBcXG5UcnVzdGluZyBtYWNoaW5lICR7aWRlbnRpdHkubWFjaGluZUlkfWApO1xuICAgIGNvbnNvbGUubG9nKGBBbGxvd2luZyBJUCAke1suLi5pcHMsIC4uLmFkZGVkXS5qb2luKFwiLCBcIil9YCk7XG4gICAgaWYgKGFkZGVkLmxlbmd0aCkge1xuICAgICAgICBjb25zb2xlLmxvZyhgICBhZGRlZCB0byBtYWNoaW5lcy8ke2lkZW50aXR5Lm1hY2hpbmVJZH0uanNvbjogJHthZGRlZC5qb2luKFwiLCBcIil9YCk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgY29uc29sZS5sb2coYCAgYWxyZWFkeSBhbGxvd2VkIGZyb20gJHthZGRyZXNzZXMuam9pbihcIiwgXCIpfWApO1xuICAgIH1cbiAgICBjb25zb2xlLmxvZyhgICBpZGVudGl0eSAke2lkZW50aXR5LmZ1bGxEb21haW59YCk7XG4gICAgY29uc29sZS5sb2coYCAgaW4gJHtyZXBvUGF0aH1gKTtcbiAgICBjb25zb2xlLmxvZyhgXFxuTm90aGluZyBiZWxpZXZlcyB0aGlzIHVudGlsIGl0IGlzIHNpZ25lZC4gSW4gdGhhdCByZXBvLCBydW46YCk7XG4gICAgY29uc29sZS5sb2coYFxcYFxcYFxcYFxcbnlhcm4gc2lnbmZpbGVzIGdpdFxcblxcYFxcYFxcYGApO1xufVxuIl19
|
|
88
|
+
/* _JS_SOURCE_HASH = "8b6a7e13912660fd6733b2585e4fec0865a5548fe5846ee6e23f46c506f83e34"; */
|