sliftutils 1.7.124 → 1.7.126
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/CLAUDE.md +3 -1
- package/bin/derivekey.js +10 -0
- package/bin/portsecuredaemon.js +13 -0
- package/bin/securessh.js +10 -0
- package/bin/setupnotify.js +9 -0
- package/bin/signfiles.js +10 -0
- package/bin/unrevoke.js +9 -0
- package/package.json +14 -3
- package/security/README.md +141 -0
- package/security/authorizedKeys/authorizedKeys.ts +66 -0
- package/security/authorizedKeys/daemon/authLog.ts +117 -0
- package/security/authorizedKeys/daemon/daemon.ts +308 -0
- package/security/authorizedKeys/daemon/git.ts +119 -0
- package/security/authorizedKeys/daemon/notify.ts +25 -0
- package/security/authorizedKeys/daemon/paths.ts +26 -0
- package/security/authorizedKeys/daemon/portsecure.service +19 -0
- package/security/authorizedKeys/daemon/revocation.ts +306 -0
- package/security/authorizedKeys/daemon/rootKeys.ts +135 -0
- package/security/authorizedKeys/daemon/sshdConfig.ts +85 -0
- package/security/authorizedKeys/daemon/state.ts +108 -0
- package/security/authorizedKeys/daemon/trust.ts +291 -0
- package/security/authorizedKeys/daemon/userKeys.ts +76 -0
- package/security/authorizedKeys/dist/authorizedKeys.ts.cache +73 -0
- package/security/authorizedKeys/dist/revokeSource.ts.cache +44 -0
- package/security/authorizedKeys/dist/secureSSH.ts.cache +552 -0
- package/security/authorizedKeys/dist/sources.ts.cache +24 -0
- package/security/authorizedKeys/dist/unrevoke.ts.cache +145 -0
- package/security/authorizedKeys/revokeSource.ts +40 -0
- package/security/authorizedKeys/secureSSH.ts +613 -0
- package/security/authorizedKeys/sources.ts +20 -0
- package/security/authorizedKeys/unrevoke.ts +149 -0
- package/security/helpers/dist/paths.ts.cache +28 -0
- package/security/helpers/dist/remoteSSH.ts.cache +90 -0
- package/security/helpers/dist/spawn.ts.cache +34 -0
- package/security/helpers/paths.ts +20 -0
- package/security/helpers/remoteSSH.ts +95 -0
- package/security/helpers/spawn.ts +36 -0
- package/security/keys/deriveKey.ts +72 -0
- package/security/keys/dist/deriveKey.ts.cache +72 -0
- package/security/keys/dist/sshKeyFile.ts.cache +153 -0
- package/security/keys/sshKeyFile.ts +156 -0
- package/security/notifications/discord.ts +190 -0
- package/security/notifications/dist/discord.ts.cache +180 -0
- package/security/notifications/remoteWebhook.ts +85 -0
- package/security/notifications/setupNotify.ts +29 -0
- package/security/signedFiles/dist/manifest.ts.cache +68 -0
- package/security/signedFiles/dist/signFiles.ts.cache +146 -0
- package/security/signedFiles/manifest.ts +69 -0
- package/security/signedFiles/signFiles.ts +151 -0
- package/storage/BulkDatabase2/dist/BulkDatabaseBase.ts.cache +17 -20
|
@@ -0,0 +1,552 @@
|
|
|
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 promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const discord_1 = require("../notifications/discord");
|
|
11
|
+
const authorizedKeys_1 = require("./authorizedKeys");
|
|
12
|
+
const sources_1 = require("./sources");
|
|
13
|
+
const revokeSource_1 = require("./revokeSource");
|
|
14
|
+
const unrevoke_1 = require("./unrevoke");
|
|
15
|
+
const paths_1 = require("../helpers/paths");
|
|
16
|
+
const spawn_1 = require("../helpers/spawn");
|
|
17
|
+
const remoteSSH_1 = require("../helpers/remoteSSH");
|
|
18
|
+
const SERVICE_SOURCE = path_1.default.join(__dirname, "daemon", "portsecure.service");
|
|
19
|
+
// The daemon runs from a checkout of this repo on the host, rather than from a copy we upload, so
|
|
20
|
+
// a host updates itself from github the same way anything else does. sliftutils is public, so this
|
|
21
|
+
// needs no key.
|
|
22
|
+
const SLIFTUTILS_URL = "https://github.com/sliftist/sliftutils.git";
|
|
23
|
+
const REMOTE_CHECKOUT_PATH = "/opt/portsecure/sliftutils";
|
|
24
|
+
const REMOTE_SERVICE_PATH = "/etc/systemd/system/portsecure.service";
|
|
25
|
+
const REMOTE_CONFIG_PATH = "/etc/portsecure/daemon.json";
|
|
26
|
+
const ROOT_AUTHORIZED_KEYS = "/root/.ssh/authorized_keys";
|
|
27
|
+
const SERVICE_NAME = "portsecure";
|
|
28
|
+
const MAX_ERROR_BODY_LENGTH = 500;
|
|
29
|
+
const VERBS = ["add", "remove", "list", "update"];
|
|
30
|
+
// The repo url is optional, and defaults to the repo the command is run from.
|
|
31
|
+
const USAGE = `Usage:
|
|
32
|
+
yarn securessh <host> add <repo-private-key> [repo-url]
|
|
33
|
+
yarn securessh <host> remove [repo-url]
|
|
34
|
+
yarn securessh <host> list
|
|
35
|
+
yarn securessh <host> update`;
|
|
36
|
+
async function pathExists(filePath) {
|
|
37
|
+
try {
|
|
38
|
+
await promises_1.default.access(filePath);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function runLocal(config) {
|
|
46
|
+
let { command, args, cwd, allowFailure } = config;
|
|
47
|
+
let result = await (0, spawn_1.spawnPromise)({ command, args, cwd });
|
|
48
|
+
if (result.error) {
|
|
49
|
+
throw new Error(`Expected ${command} to run, failed with ${result.error.message}`);
|
|
50
|
+
}
|
|
51
|
+
if (result.status !== 0 && !allowFailure) {
|
|
52
|
+
throw new Error(`Expected ${command} ${args.join(" ")} to exit 0, was ${result.status}. `
|
|
53
|
+
+ `${(result.stdout + result.stderr).trim().slice(0, MAX_ERROR_BODY_LENGTH)}`);
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
/** A private key cannot authenticate an https remote, so github urls are converted to the ssh form
|
|
58
|
+
the key can actually be used with. */
|
|
59
|
+
function normalizeRepoURL(repoURL) {
|
|
60
|
+
let httpsMatch = repoURL.match(/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?\/?$/);
|
|
61
|
+
if (!httpsMatch) {
|
|
62
|
+
return repoURL;
|
|
63
|
+
}
|
|
64
|
+
let [, host, repoPath] = httpsMatch;
|
|
65
|
+
return `git@${host}:${repoPath}.git`;
|
|
66
|
+
}
|
|
67
|
+
async function gitWithKey(config) {
|
|
68
|
+
let { keyPath, args, cwd, allowFailure } = config;
|
|
69
|
+
// core.sshCommand keeps key selection with the command instead of in the environment.
|
|
70
|
+
let sshCommand = `ssh -i ${keyPath} -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new`;
|
|
71
|
+
let result = await (0, spawn_1.spawnPromise)({ command: "git", args: ["-c", `core.sshCommand=${sshCommand}`, ...args], cwd });
|
|
72
|
+
if (result.error) {
|
|
73
|
+
throw new Error(`Expected git to run, failed with ${result.error.message}`);
|
|
74
|
+
}
|
|
75
|
+
if (result.status !== 0 && !allowFailure) {
|
|
76
|
+
throw new Error(`Expected git ${args.join(" ")} to exit 0, was ${result.status}. `
|
|
77
|
+
+ `${(result.stdout + result.stderr).trim().slice(0, MAX_ERROR_BODY_LENGTH)}`);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
/** Asks ssh which key it actually authenticated with. This is the key that must survive the
|
|
82
|
+
daemon taking over authorized_keys, otherwise a deploy locks us out. */
|
|
83
|
+
async function findAuthenticatingFingerprint(host) {
|
|
84
|
+
let result = await (0, spawn_1.spawnPromise)({
|
|
85
|
+
command: "ssh",
|
|
86
|
+
args: ["-v", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", host, "true"],
|
|
87
|
+
});
|
|
88
|
+
let verboseOutput = result.stderr || "";
|
|
89
|
+
if (result.status !== 0) {
|
|
90
|
+
throw new Error(`Expected to ssh into ${host}, failed. ${verboseOutput.slice(-MAX_ERROR_BODY_LENGTH)}`);
|
|
91
|
+
}
|
|
92
|
+
let acceptedMatch = verboseOutput.match(/Server accepts key:.*?(SHA256:[A-Za-z0-9+/=]+)/);
|
|
93
|
+
if (!acceptedMatch) {
|
|
94
|
+
throw new Error(`Expected ${host} to accept a public key, but the session did not authenticate with one.`
|
|
95
|
+
+ ` portsecure disables password login, so key based access has to work first.`);
|
|
96
|
+
}
|
|
97
|
+
return acceptedMatch[1];
|
|
98
|
+
}
|
|
99
|
+
async function fingerprintKeys(keys) {
|
|
100
|
+
if (!keys.length) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
let temporaryDirectory = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "portsecure-keys-"));
|
|
104
|
+
let keysPath = path_1.default.join(temporaryDirectory, "authorized_keys");
|
|
105
|
+
await promises_1.default.writeFile(keysPath, `${keys.join("\n")}\n`);
|
|
106
|
+
let result = await runLocal({ command: "ssh-keygen", args: ["-lf", keysPath], allowFailure: true });
|
|
107
|
+
await promises_1.default.rm(temporaryDirectory, { recursive: true, force: true });
|
|
108
|
+
let fingerprints = [];
|
|
109
|
+
for (let line of result.stdout.split("\n")) {
|
|
110
|
+
let match = line.match(/(SHA256:[A-Za-z0-9+/=]+)/);
|
|
111
|
+
if (match) {
|
|
112
|
+
fingerprints.push(match[1]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return fingerprints;
|
|
116
|
+
}
|
|
117
|
+
async function cloneRepoForInspection(config) {
|
|
118
|
+
let { repoURL, keyPath } = config;
|
|
119
|
+
let temporaryDirectory = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "portsecure-repo-"));
|
|
120
|
+
let repoPath = path_1.default.join(temporaryDirectory, "repo");
|
|
121
|
+
await gitWithKey({ keyPath, args: ["clone", "--depth", "1", repoURL, repoPath] });
|
|
122
|
+
return repoPath;
|
|
123
|
+
}
|
|
124
|
+
/** With no repo url given, the repo we are standing in is used. It has to actually hold keys
|
|
125
|
+
before we hand it to a host, so a stray working directory cannot be deployed by accident. */
|
|
126
|
+
async function resolveRepoURL(passedURL) {
|
|
127
|
+
if (passedURL) {
|
|
128
|
+
return normalizeRepoURL(passedURL);
|
|
129
|
+
}
|
|
130
|
+
let topLevel = await runLocal({ command: "git", args: ["rev-parse", "--show-toplevel"], allowFailure: true });
|
|
131
|
+
if (topLevel.status !== 0) {
|
|
132
|
+
throw new Error(`Expected a repo url, or the current directory to be inside a git repo, it is not.\n${USAGE}`);
|
|
133
|
+
}
|
|
134
|
+
let repoPath = topLevel.stdout.trim();
|
|
135
|
+
try {
|
|
136
|
+
await (0, authorizedKeys_1.readRepoKeys)(repoPath);
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
throw new Error(`Expected a repo url, or the current repo (${repoPath}) to hold keys, it does not.\n${e}\n${USAGE}`);
|
|
140
|
+
}
|
|
141
|
+
let origin = await runLocal({
|
|
142
|
+
command: "git",
|
|
143
|
+
args: ["remote", "get-url", "origin"],
|
|
144
|
+
cwd: repoPath,
|
|
145
|
+
allowFailure: true,
|
|
146
|
+
});
|
|
147
|
+
if (origin.status !== 0 || !origin.stdout.trim()) {
|
|
148
|
+
throw new Error(`Expected the current repo (${repoPath}) to have an origin remote, it has none.`
|
|
149
|
+
+ ` The host clones the source itself, so a local path is no use to it.`);
|
|
150
|
+
}
|
|
151
|
+
let repoURL = normalizeRepoURL(origin.stdout.trim());
|
|
152
|
+
console.log(`No repo url given, using the current repo: ${repoURL}`);
|
|
153
|
+
return repoURL;
|
|
154
|
+
}
|
|
155
|
+
/** Every source's revoke repo has to exist and hold at least one commit, or the hosts using it
|
|
156
|
+
cannot record a revocation. Checked with this machine's own git credentials, since update is
|
|
157
|
+
not given any deploy key, and an empty repo is initialised rather than merely complained about.
|
|
158
|
+
|
|
159
|
+
A host that cannot write a revocation silently keeps accepting a key it just saw being misused,
|
|
160
|
+
which is the one failure this whole thing exists to prevent, so it is checked on every deploy
|
|
161
|
+
and not only when a source is first added. */
|
|
162
|
+
async function ensureRevokeReposExist(repoSources) {
|
|
163
|
+
for (let repoURL of repoSources) {
|
|
164
|
+
let revokeURL = (0, revokeSource_1.revokeRepoURL)(repoURL);
|
|
165
|
+
let temporaryDirectory = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "portsecure-revoke-"));
|
|
166
|
+
let checkoutPath = path_1.default.join(temporaryDirectory, "repo");
|
|
167
|
+
let clone = await runLocal({ command: "git", args: ["clone", revokeURL, checkoutPath], allowFailure: true });
|
|
168
|
+
if (clone.status !== 0) {
|
|
169
|
+
await promises_1.default.rm(temporaryDirectory, { recursive: true, force: true });
|
|
170
|
+
throw new Error(`Expected ${revokeURL} to exist, it does not, so ${repoURL} has nowhere to record a`
|
|
171
|
+
+ ` revocation.\nCreate it, then run "yarn securessh <host> add" for that source to`
|
|
172
|
+
+ ` register its deploy key.\n${(clone.stdout + clone.stderr).trim().slice(0, MAX_ERROR_BODY_LENGTH)}`);
|
|
173
|
+
}
|
|
174
|
+
let head = await runLocal({ command: "git", args: ["-C", checkoutPath, "rev-parse", "HEAD"], allowFailure: true });
|
|
175
|
+
if (head.status !== 0) {
|
|
176
|
+
console.log(`${revokeURL} is empty, giving it a first commit`);
|
|
177
|
+
await promises_1.default.writeFile(path_1.default.join(checkoutPath, "README.md"), `# revoked keys\n\nWritten by portsecure. Each file under revocations/ is one key that was used from an\n`
|
|
178
|
+
+ `address it is not allowed from, and is no longer accepted anywhere.\n`);
|
|
179
|
+
for (let args of [
|
|
180
|
+
["-C", checkoutPath, "add", "-A"],
|
|
181
|
+
["-C", checkoutPath, "-c", "user.email=portsecure@localhost", "-c", "user.name=portsecure", "commit", "-m", "initialise revoke repo"],
|
|
182
|
+
["-C", checkoutPath, "push", "origin", "HEAD"],
|
|
183
|
+
]) {
|
|
184
|
+
await runLocal({ command: "git", args });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
await promises_1.default.rm(temporaryDirectory, { recursive: true, force: true });
|
|
188
|
+
console.log(`${revokeURL} is ready`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/** The revoke repo has to exist and be writable before a host is set up, because a host that
|
|
192
|
+
cannot write a revocation cannot revoke a key that is being misused. The key for it is derived
|
|
193
|
+
from the source's, since github will not take one public key on two repos. */
|
|
194
|
+
async function ensureRevokeRepo(config) {
|
|
195
|
+
let { keyPath, repoURL } = config;
|
|
196
|
+
let revokeURL = (0, revokeSource_1.revokeRepoURL)(repoURL);
|
|
197
|
+
let derived = (0, revokeSource_1.deriveRevokeKey)(await promises_1.default.readFile(keyPath, "utf8"));
|
|
198
|
+
let temporaryDirectory = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "portsecure-revoke-"));
|
|
199
|
+
let derivedKeyPath = path_1.default.join(temporaryDirectory, "key");
|
|
200
|
+
await promises_1.default.writeFile(derivedKeyPath, derived.privateKeyFile, { mode: 0o600 });
|
|
201
|
+
let checkoutPath = path_1.default.join(temporaryDirectory, "repo");
|
|
202
|
+
let explain = (problem) => new Error(`${problem}\n`
|
|
203
|
+
+ `Create ${revokeURL} and add this as a deploy key WITH WRITE ACCESS:\n`
|
|
204
|
+
+ ` ${derived.publicKey} ${revokeSource_1.REVOKE_KEY_LABEL}\n`
|
|
205
|
+
+ `It has to be this key: it is derived from ${keyPath}, and github will not accept the same`
|
|
206
|
+
+ ` public key on two repositories.`);
|
|
207
|
+
console.log(`Checking ${revokeURL}`);
|
|
208
|
+
let clone = await gitWithKey({ keyPath: derivedKeyPath, args: ["clone", revokeURL, checkoutPath], allowFailure: true });
|
|
209
|
+
if (clone.status !== 0) {
|
|
210
|
+
throw explain(`Expected ${revokeURL} to be readable with the derived key, it is not.`);
|
|
211
|
+
}
|
|
212
|
+
// An empty repo has no branch for the daemon to clone, so it gets its first commit here. That
|
|
213
|
+
// doubles as the proof that we can write to it.
|
|
214
|
+
let head = await gitWithKey({ keyPath: derivedKeyPath, args: ["rev-parse", "HEAD"], cwd: checkoutPath, allowFailure: true });
|
|
215
|
+
if (head.status !== 0) {
|
|
216
|
+
await promises_1.default.writeFile(path_1.default.join(checkoutPath, "README.md"), `# revoked keys\n\nWritten by portsecure. Each file under revocations/ is one key that was used from an\n`
|
|
217
|
+
+ `address it is not allowed from, and is no longer accepted anywhere.\n`);
|
|
218
|
+
for (let args of [
|
|
219
|
+
["add", "-A"],
|
|
220
|
+
["-c", "user.email=portsecure@localhost", "-c", "user.name=portsecure", "commit", "-m", "initialise revoke repo"],
|
|
221
|
+
]) {
|
|
222
|
+
await gitWithKey({ keyPath: derivedKeyPath, args, cwd: checkoutPath });
|
|
223
|
+
}
|
|
224
|
+
let push = await gitWithKey({ keyPath: derivedKeyPath, args: ["push", "origin", "HEAD"], cwd: checkoutPath, allowFailure: true });
|
|
225
|
+
if (push.status !== 0) {
|
|
226
|
+
throw explain(`Expected write access to ${revokeURL}, the first push was refused.\n${(push.stdout + push.stderr).trim().slice(0, MAX_ERROR_BODY_LENGTH)}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
let dryRun = await gitWithKey({ keyPath: derivedKeyPath, args: ["push", "--dry-run", "origin", "HEAD"], cwd: checkoutPath, allowFailure: true });
|
|
231
|
+
if (dryRun.status !== 0) {
|
|
232
|
+
throw explain(`Expected write access to ${revokeURL}, a dry run push was refused.\n${(dryRun.stdout + dryRun.stderr).trim().slice(0, MAX_ERROR_BODY_LENGTH)}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
await promises_1.default.rm(temporaryDirectory, { recursive: true, force: true });
|
|
236
|
+
console.log(`${revokeURL} is writable`);
|
|
237
|
+
}
|
|
238
|
+
async function readRemoteConfig(host) {
|
|
239
|
+
let contents = await (0, remoteSSH_1.readRemoteFile)({ host, filePath: REMOTE_CONFIG_PATH });
|
|
240
|
+
if (!contents) {
|
|
241
|
+
return { hostLabel: host, repoSources: [] };
|
|
242
|
+
}
|
|
243
|
+
let parsed = JSON.parse(contents);
|
|
244
|
+
return { hostLabel: parsed.hostLabel || host, repoSources: parsed.repoSources || [] };
|
|
245
|
+
}
|
|
246
|
+
/** Reads the keys a source's checkout currently holds on the host, so the merged result can be
|
|
247
|
+
worked out without needing that source's private key locally. */
|
|
248
|
+
async function readRemoteSourceKeys(config) {
|
|
249
|
+
let { host, repoURL } = config;
|
|
250
|
+
let repoPath = (0, sources_1.sourceRepoPath)(repoURL);
|
|
251
|
+
let output = await (0, remoteSSH_1.runOverSSH)({
|
|
252
|
+
host,
|
|
253
|
+
script: `${remoteSSH_1.SUDO_PREAMBLE}
|
|
254
|
+
if $SUDO test -f "${repoPath}/authorized_keys"; then
|
|
255
|
+
$SUDO cat "${repoPath}/authorized_keys"
|
|
256
|
+
elif $SUDO test -d "${repoPath}"; then
|
|
257
|
+
$SUDO cat "${repoPath}"/*.pub 2>/dev/null || true
|
|
258
|
+
fi`,
|
|
259
|
+
allowFailure: true,
|
|
260
|
+
});
|
|
261
|
+
return (0, authorizedKeys_1.normalizeKeys)(output.stdout);
|
|
262
|
+
}
|
|
263
|
+
async function installDaemon(config) {
|
|
264
|
+
let { host, hostLabel, repoSources } = config;
|
|
265
|
+
for (let command of ["node", "git", "yarn"]) {
|
|
266
|
+
if (!await (0, remoteSSH_1.remoteCommandExists)({ host, command })) {
|
|
267
|
+
throw new Error(`Expected ${command} to be installed on ${host}, it is not. Install it and rerun.`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// The checkout is brought to the latest commit rather than a copy being pushed, so what runs on
|
|
271
|
+
// the host is exactly what is on github.
|
|
272
|
+
console.log(`Updating ${REMOTE_CHECKOUT_PATH} on ${host}`);
|
|
273
|
+
await (0, remoteSSH_1.runOverSSH)({
|
|
274
|
+
host,
|
|
275
|
+
script: `${remoteSSH_1.SUDO_PREAMBLE}
|
|
276
|
+
set -e
|
|
277
|
+
$SUDO mkdir -p "${path_1.default.posix.dirname(REMOTE_CHECKOUT_PATH)}"
|
|
278
|
+
if $SUDO test -d "${REMOTE_CHECKOUT_PATH}/.git"; then
|
|
279
|
+
$SUDO git -C "${REMOTE_CHECKOUT_PATH}" fetch --prune origin
|
|
280
|
+
$SUDO git -C "${REMOTE_CHECKOUT_PATH}" reset --hard origin/HEAD
|
|
281
|
+
else
|
|
282
|
+
$SUDO rm -rf "${REMOTE_CHECKOUT_PATH}"
|
|
283
|
+
$SUDO git clone "${SLIFTUTILS_URL}" "${REMOTE_CHECKOUT_PATH}"
|
|
284
|
+
fi
|
|
285
|
+
$SUDO yarn --cwd "${REMOTE_CHECKOUT_PATH}" install --production --non-interactive
|
|
286
|
+
# The single file daemon this replaced, left over on a host set up by an older version.
|
|
287
|
+
$SUDO rm -f /opt/portsecure/portsecure-daemon.js`,
|
|
288
|
+
});
|
|
289
|
+
await (0, remoteSSH_1.writeRemoteFile)({
|
|
290
|
+
host,
|
|
291
|
+
filePath: REMOTE_CONFIG_PATH,
|
|
292
|
+
// Only what differs between machines. Every path the daemon uses is derived in the daemon
|
|
293
|
+
// itself, so there is nothing here to drift out of sync.
|
|
294
|
+
contents: JSON.stringify({ hostLabel, repoSources }, undefined, 4) + "\n",
|
|
295
|
+
fileMode: "600",
|
|
296
|
+
directoryMode: "700",
|
|
297
|
+
});
|
|
298
|
+
await (0, remoteSSH_1.writeRemoteFile)({
|
|
299
|
+
host,
|
|
300
|
+
filePath: REMOTE_SERVICE_PATH,
|
|
301
|
+
contents: await promises_1.default.readFile(SERVICE_SOURCE, "utf8"),
|
|
302
|
+
fileMode: "644",
|
|
303
|
+
directoryMode: "755",
|
|
304
|
+
});
|
|
305
|
+
await (0, remoteSSH_1.runOverSSH)({
|
|
306
|
+
host,
|
|
307
|
+
script: `${remoteSSH_1.SUDO_PREAMBLE}
|
|
308
|
+
set -e
|
|
309
|
+
$SUDO systemctl daemon-reload
|
|
310
|
+
$SUDO systemctl enable ${SERVICE_NAME}
|
|
311
|
+
$SUDO systemctl restart ${SERVICE_NAME}`,
|
|
312
|
+
});
|
|
313
|
+
let status = (await (0, remoteSSH_1.runOverSSH)({
|
|
314
|
+
host,
|
|
315
|
+
script: `systemctl is-active ${SERVICE_NAME} || true`,
|
|
316
|
+
allowFailure: true,
|
|
317
|
+
})).stdout.trim();
|
|
318
|
+
if (status !== "active") {
|
|
319
|
+
let journal = (await (0, remoteSSH_1.runOverSSH)({
|
|
320
|
+
host,
|
|
321
|
+
script: `${remoteSSH_1.SUDO_PREAMBLE}
|
|
322
|
+
$SUDO journalctl -u ${SERVICE_NAME} -n 40 --no-pager || true`,
|
|
323
|
+
allowFailure: true,
|
|
324
|
+
})).stdout;
|
|
325
|
+
throw new Error(`Expected ${SERVICE_NAME} to be active on ${host}, was ${status}.\n${journal.slice(-2000)}`);
|
|
326
|
+
}
|
|
327
|
+
let stillReachable = await (0, remoteSSH_1.runOverSSH)({ host, script: "echo reachable", allowFailure: true });
|
|
328
|
+
if (stillReachable.stdout.trim() !== "reachable") {
|
|
329
|
+
throw new Error(`Expected ${host} to still be reachable after the daemon started, it is not.`
|
|
330
|
+
+ ` Check console access immediately.`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
async function requireRemoteWebhook(host) {
|
|
334
|
+
let contents = await (0, remoteSSH_1.readRemoteFile)({ host, filePath: discord_1.DEFAULT_WEBHOOK_FILE_PATH });
|
|
335
|
+
if (!contents) {
|
|
336
|
+
throw new Error(`Expected a Discord webhook at ${discord_1.DEFAULT_WEBHOOK_FILE_PATH} on ${host}, no such file exists.`
|
|
337
|
+
+ ` The daemon will not start without one.\n`
|
|
338
|
+
+ `Set it up first:\n yarn setupnotify ${host} <discord-webhook-url>`);
|
|
339
|
+
}
|
|
340
|
+
return (0, discord_1.parseWebhookFile)({ contents, sourceName: `${host}:${discord_1.DEFAULT_WEBHOOK_FILE_PATH}` });
|
|
341
|
+
}
|
|
342
|
+
async function addSource(config) {
|
|
343
|
+
let { host, keyPath, repoURL } = config;
|
|
344
|
+
if (!await pathExists(keyPath)) {
|
|
345
|
+
throw new Error(`Expected a private key at ${keyPath}, no such file exists`);
|
|
346
|
+
}
|
|
347
|
+
console.log(`Checking ${repoURL} is reachable with ${keyPath}`);
|
|
348
|
+
let reachable = await gitWithKey({ keyPath, args: ["ls-remote", repoURL], allowFailure: true });
|
|
349
|
+
if (reachable.status !== 0) {
|
|
350
|
+
throw new Error(`Expected ${repoURL} to be reachable with ${keyPath}, git ls-remote failed.`
|
|
351
|
+
+ ` The daemon would have no way to fetch keys.\n`
|
|
352
|
+
+ `${(reachable.stdout + reachable.stderr).trim().slice(0, MAX_ERROR_BODY_LENGTH)}`);
|
|
353
|
+
}
|
|
354
|
+
let remoteConfig = await readRemoteConfig(host);
|
|
355
|
+
if (remoteConfig.repoSources.includes(repoURL)) {
|
|
356
|
+
console.log(`${host} already has ${repoURL}, refreshing its key and the daemon.`);
|
|
357
|
+
}
|
|
358
|
+
// The merged result is what root ends up with, so our own key has to be somewhere in it.
|
|
359
|
+
console.log(`Checking our access to ${host} survives the merged keys`);
|
|
360
|
+
let ourFingerprint = await findAuthenticatingFingerprint(host);
|
|
361
|
+
let inspectionPath = await cloneRepoForInspection({ repoURL, keyPath });
|
|
362
|
+
let newKeys = await (0, authorizedKeys_1.readRepoKeys)(inspectionPath);
|
|
363
|
+
// Whatever is applied on the host came from the existing sources, so it stays in the merge.
|
|
364
|
+
let existingKeys = (0, authorizedKeys_1.normalizeKeys)(await (0, remoteSSH_1.readRemoteFile)({ host, filePath: ROOT_AUTHORIZED_KEYS }) || "");
|
|
365
|
+
let mergedFingerprints = await fingerprintKeys([...existingKeys, ...newKeys]);
|
|
366
|
+
if (!mergedFingerprints.includes(ourFingerprint)) {
|
|
367
|
+
throw new Error(`Expected the key we use for ${host} to be in the merged keys, it is not.\n`
|
|
368
|
+
+ `Ours: ${ourFingerprint}\n`
|
|
369
|
+
+ `Merged: ${mergedFingerprints.join("\n ") || "(none)"}\n`
|
|
370
|
+
+ `The daemon replaces root's authorized_keys with the merged sources, so this would`
|
|
371
|
+
+ ` lock you out of ${host}. Add your public key to ${repoURL} first.`);
|
|
372
|
+
}
|
|
373
|
+
console.log(`Our key ${ourFingerprint} is in the merged keys, access will survive.`);
|
|
374
|
+
await ensureRevokeRepo({ keyPath, repoURL });
|
|
375
|
+
// Deploying a repo that still holds a revoked key would hand it back to every machine.
|
|
376
|
+
let revoked = await (0, unrevoke_1.revokedKeysInRepo)({ repoPath: inspectionPath, sourceURL: repoURL });
|
|
377
|
+
if (revoked.length) {
|
|
378
|
+
throw new Error(`Expected ${repoURL} to hold no revoked keys, it holds ${revoked.length}:\n`
|
|
379
|
+
+ revoked.map(entry => {
|
|
380
|
+
var _a;
|
|
381
|
+
return ` ${entry.revocation.fingerprint} revoked by`
|
|
382
|
+
+ ` ${entry.revocation.revokedBy || "?"} after use from ${((_a = entry.revocation.attempt) === null || _a === void 0 ? void 0 : _a.ip) || "?"}`;
|
|
383
|
+
}).join("\n")
|
|
384
|
+
+ `\nDelete them from the repo, or run "yarn unrevoke" there to allow them again.`);
|
|
385
|
+
}
|
|
386
|
+
let webhookURL = await requireRemoteWebhook(host);
|
|
387
|
+
console.log(`${host} notifies ${webhookURL}`);
|
|
388
|
+
await (0, remoteSSH_1.writeRemoteFile)({
|
|
389
|
+
host,
|
|
390
|
+
filePath: (0, sources_1.sourceKeyPath)(repoURL),
|
|
391
|
+
contents: await promises_1.default.readFile(keyPath, "utf8"),
|
|
392
|
+
fileMode: "600",
|
|
393
|
+
directoryMode: "700",
|
|
394
|
+
});
|
|
395
|
+
let repoSources = remoteConfig.repoSources.filter(source => source !== repoURL);
|
|
396
|
+
repoSources.push(repoURL);
|
|
397
|
+
await installDaemon({ host, hostLabel: remoteConfig.hostLabel, repoSources });
|
|
398
|
+
console.log(`${repoURL} added to ${host}. ${repoSources.length} source(s) now merged.`);
|
|
399
|
+
}
|
|
400
|
+
async function removeSource(config) {
|
|
401
|
+
let { host, repoURL } = config;
|
|
402
|
+
let remoteConfig = await readRemoteConfig(host);
|
|
403
|
+
if (!remoteConfig.repoSources.includes(repoURL)) {
|
|
404
|
+
throw new Error(`Expected ${repoURL} to be a source on ${host}, it is not.\n`
|
|
405
|
+
+ `Configured:\n ${remoteConfig.repoSources.join("\n ") || "(none)"}`);
|
|
406
|
+
}
|
|
407
|
+
let repoSources = remoteConfig.repoSources.filter(source => source !== repoURL);
|
|
408
|
+
if (repoSources.length) {
|
|
409
|
+
// The keys left over are what root gets, so our own key has to be among them.
|
|
410
|
+
console.log(`Checking our access to ${host} survives without ${repoURL}`);
|
|
411
|
+
let ourFingerprint = await findAuthenticatingFingerprint(host);
|
|
412
|
+
let remainingKeys = [];
|
|
413
|
+
for (let source of repoSources) {
|
|
414
|
+
remainingKeys.push(...await readRemoteSourceKeys({ host, repoURL: source }));
|
|
415
|
+
}
|
|
416
|
+
let remainingFingerprints = await fingerprintKeys(remainingKeys);
|
|
417
|
+
if (!remainingFingerprints.includes(ourFingerprint)) {
|
|
418
|
+
throw new Error(`Expected the key we use for ${host} to still be in the remaining sources, it is not.\n`
|
|
419
|
+
+ `Ours: ${ourFingerprint}\n`
|
|
420
|
+
+ `Remaining: ${remainingFingerprints.join("\n ") || "(none)"}\n`
|
|
421
|
+
+ `Removing ${repoURL} would lock you out of ${host}.`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
// Nothing left to merge, so the daemon leaves root's authorized_keys exactly as it is.
|
|
426
|
+
console.log(`${repoURL} is the last source, so root's authorized_keys stays as it is now.`);
|
|
427
|
+
}
|
|
428
|
+
await requireRemoteWebhook(host);
|
|
429
|
+
await (0, remoteSSH_1.runOverSSH)({
|
|
430
|
+
host,
|
|
431
|
+
script: `${remoteSSH_1.SUDO_PREAMBLE}
|
|
432
|
+
$SUDO rm -f "${(0, sources_1.sourceKeyPath)(repoURL)}"
|
|
433
|
+
$SUDO rm -rf "${(0, sources_1.sourceRepoPath)(repoURL)}"`,
|
|
434
|
+
});
|
|
435
|
+
await installDaemon({ host, hostLabel: remoteConfig.hostLabel, repoSources });
|
|
436
|
+
console.log(`${repoURL} removed from ${host}. ${repoSources.length} source(s) left.`);
|
|
437
|
+
}
|
|
438
|
+
/** Answers "who can log into this box, and which repo says so". The paths the daemon uses are
|
|
439
|
+
left out on purpose, they are plumbing rather than something to act on. */
|
|
440
|
+
/** The daemon is installed from this checkout, so the checkout is brought up to date first.
|
|
441
|
+
Anything that stops the pull - a conflict, local commits, no upstream - stops the update too,
|
|
442
|
+
rather than quietly installing whatever happened to be on disk. */
|
|
443
|
+
async function pullLocalCheckout() {
|
|
444
|
+
let topLevel = await (0, spawn_1.spawnPromise)({ command: "git", args: ["rev-parse", "--show-toplevel"], cwd: __dirname });
|
|
445
|
+
if (topLevel.status !== 0) {
|
|
446
|
+
throw new Error(`Expected ${__dirname} to be inside a git checkout, it is not.`
|
|
447
|
+
+ ` update installs the daemon from this checkout, so it has to be one.`);
|
|
448
|
+
}
|
|
449
|
+
let repoPath = topLevel.stdout.trim();
|
|
450
|
+
let pull = await (0, spawn_1.spawnPromise)({ command: "git", args: ["pull", "--ff-only"], cwd: repoPath });
|
|
451
|
+
if (pull.status !== 0) {
|
|
452
|
+
throw new Error(`Expected git pull in ${repoPath} to succeed, it did not, so nothing was installed.\n`
|
|
453
|
+
+ `${(pull.stdout + pull.stderr).trim().slice(0, MAX_ERROR_BODY_LENGTH)}`);
|
|
454
|
+
}
|
|
455
|
+
// The host installs from github, not from here, so local commits that have not been pushed
|
|
456
|
+
// are not what it will run.
|
|
457
|
+
let local = await (0, spawn_1.spawnPromise)({ command: "git", args: ["rev-parse", "HEAD"], cwd: repoPath });
|
|
458
|
+
let remote = await (0, spawn_1.spawnPromise)({ command: "git", args: ["rev-parse", "@{u}"], cwd: repoPath });
|
|
459
|
+
if (local.stdout.trim() !== remote.stdout.trim()) {
|
|
460
|
+
console.log(`WARNING: ${repoPath} has commits that are not pushed. The host installs from github,`);
|
|
461
|
+
console.log(` so it will run the pushed version, not what is here.`);
|
|
462
|
+
}
|
|
463
|
+
console.log(`Pulled ${repoPath}`);
|
|
464
|
+
}
|
|
465
|
+
/** Pushes the current daemon onto a host that already has one, for when this code has moved on.
|
|
466
|
+
Nothing about which keys the host trusts is touched. */
|
|
467
|
+
async function updateDaemon(host) {
|
|
468
|
+
// Pulled before anything else, so a checkout that cannot be brought up to date fails here
|
|
469
|
+
// rather than after we have already started changing the host.
|
|
470
|
+
await pullLocalCheckout();
|
|
471
|
+
let contents = await (0, remoteSSH_1.readRemoteFile)({ host, filePath: REMOTE_CONFIG_PATH });
|
|
472
|
+
if (!contents) {
|
|
473
|
+
throw new Error(`Expected ${host} to already have portsecure, ${REMOTE_CONFIG_PATH} does not exist.\n`
|
|
474
|
+
+ `Set it up with:\n yarn securessh ${host} add <repo-private-key> [repo-url]`);
|
|
475
|
+
}
|
|
476
|
+
let parsed = JSON.parse(contents);
|
|
477
|
+
let repoSources = parsed.repoSources || [];
|
|
478
|
+
await requireRemoteWebhook(host);
|
|
479
|
+
await ensureRevokeReposExist(repoSources);
|
|
480
|
+
await installDaemon({ host, hostLabel: parsed.hostLabel || host, repoSources });
|
|
481
|
+
console.log(`Updated the daemon on ${host}. ${repoSources.length} source(s), unchanged.`);
|
|
482
|
+
}
|
|
483
|
+
async function listSources(host) {
|
|
484
|
+
let remoteConfig = await readRemoteConfig(host);
|
|
485
|
+
if (!remoteConfig.repoSources.length) {
|
|
486
|
+
console.log(`${host} has no key sources. root's authorized_keys is left exactly as it is.`);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
console.log(`${host} lets root log in with the keys from ${remoteConfig.repoSources.length} repo(s):`);
|
|
490
|
+
let merged = new Set();
|
|
491
|
+
for (let repoURL of remoteConfig.repoSources) {
|
|
492
|
+
let keys = await readRemoteSourceKeys({ host, repoURL });
|
|
493
|
+
console.log(`\n ${repoURL}`);
|
|
494
|
+
if (!keys.length) {
|
|
495
|
+
console.log(` grants no keys - the checkout is missing or empty`);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
console.log(` grants ${keys.length} key(s):`);
|
|
499
|
+
for (let key of keys) {
|
|
500
|
+
console.log(` ${(0, authorizedKeys_1.summarizeKey)(key)}`);
|
|
501
|
+
merged.add(key);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (remoteConfig.repoSources.length > 1) {
|
|
505
|
+
console.log(`\n${merged.size} key(s) in total once duplicates are merged.`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/** The verb is a fixed word rather than a position, so it is pulled out of the arguments wherever
|
|
509
|
+
it was typed and everything left over is positional. */
|
|
510
|
+
function parseArgs(argv) {
|
|
511
|
+
let verbs = argv.filter(arg => VERBS.includes(arg));
|
|
512
|
+
if (!verbs.length) {
|
|
513
|
+
throw new Error(`Expected one of ${VERBS.join(", ")} somewhere in the arguments, was ${argv.join(" ") || "(nothing)"}\n${USAGE}`);
|
|
514
|
+
}
|
|
515
|
+
if (verbs.length > 1) {
|
|
516
|
+
throw new Error(`Expected one of ${VERBS.join(", ")}, was ${verbs.join(" and ")}\n${USAGE}`);
|
|
517
|
+
}
|
|
518
|
+
let verb = verbs[0];
|
|
519
|
+
let [host, ...rest] = argv.filter(arg => arg !== verb);
|
|
520
|
+
if (!host) {
|
|
521
|
+
throw new Error(`Expected a host, was nothing\n${USAGE}`);
|
|
522
|
+
}
|
|
523
|
+
return { verb, host, rest };
|
|
524
|
+
}
|
|
525
|
+
async function main() {
|
|
526
|
+
let { verb, host, rest } = parseArgs(process.argv.slice(2));
|
|
527
|
+
if (verb === "list") {
|
|
528
|
+
await listSources(host);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (verb === "update") {
|
|
532
|
+
if (rest.length) {
|
|
533
|
+
throw new Error(`Expected nothing after update, was ${rest.length} argument(s)\n${USAGE}`);
|
|
534
|
+
}
|
|
535
|
+
await updateDaemon(host);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (verb === "add") {
|
|
539
|
+
if (!rest.length || rest.length > 2) {
|
|
540
|
+
throw new Error(`Expected a private key and optionally a repo url, was ${rest.length} argument(s)\n${USAGE}`);
|
|
541
|
+
}
|
|
542
|
+
await addSource({ host, keyPath: (0, paths_1.expandHome)(rest[0]), repoURL: await resolveRepoURL(rest[1]) });
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
if (rest.length > 1) {
|
|
546
|
+
throw new Error(`Expected at most a repo url to remove, was ${rest.length} argument(s)\n${USAGE}`);
|
|
547
|
+
}
|
|
548
|
+
await removeSource({ host, repoURL: await resolveRepoURL(rest[0]) });
|
|
549
|
+
}
|
|
550
|
+
exports.main = main;
|
|
551
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VjdXJlU1NILmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsic2VjdXJlU1NILnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUFBLDJEQUE2QjtBQUM3Qiw0Q0FBb0I7QUFDcEIsZ0RBQXdCO0FBQ3hCLHNEQUF1RjtBQUN2RixxREFBNkU7QUFDN0UsdUNBQTBEO0FBQzFELGlEQUFrRjtBQUNsRix5Q0FBK0M7QUFDL0MsNENBQThDO0FBQzlDLDRDQUFnRDtBQUNoRCxvREFBdUg7QUFFdkgsTUFBTSxjQUFjLEdBQUcsY0FBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsUUFBUSxFQUFFLG9CQUFvQixDQUFDLENBQUM7QUFDNUUsa0dBQWtHO0FBQ2xHLG1HQUFtRztBQUNuRyxnQkFBZ0I7QUFDaEIsTUFBTSxjQUFjLEdBQUcsNENBQTRDLENBQUM7QUFDcEUsTUFBTSxvQkFBb0IsR0FBRyw0QkFBNEIsQ0FBQztBQUMxRCxNQUFNLG1CQUFtQixHQUFHLHdDQUF3QyxDQUFDO0FBQ3JFLE1BQU0sa0JBQWtCLEdBQUcsNkJBQTZCLENBQUM7QUFDekQsTUFBTSxvQkFBb0IsR0FBRyw0QkFBNEIsQ0FBQztBQUMxRCxNQUFNLFlBQVksR0FBRyxZQUFZLENBQUM7QUFDbEMsTUFBTSxxQkFBcUIsR0FBRyxHQUFHLENBQUM7QUFDbEMsTUFBTSxLQUFLLEdBQUcsQ0FBQyxLQUFLLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxRQUFRLENBQUMsQ0FBQztBQUNsRCw4RUFBOEU7QUFDOUUsTUFBTSxLQUFLLEdBQUc7Ozs7K0JBSWlCLENBQUM7QUFFaEMsS0FBSyxVQUFVLFVBQVUsQ0FBQyxRQUFnQjtJQUN0QyxJQUFJLENBQUM7UUFDRCxNQUFNLGtCQUFFLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQzFCLE9BQU8sSUFBSSxDQUFDO0lBQ2hCLENBQUM7SUFBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1FBQ1QsT0FBTyxLQUFLLENBQUM7SUFDakIs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aUIsS0FBSyxFQUFFLENBQUMsQ0FBQztRQUNsSCxDQUFDO1FBQ0QsTUFBTSxTQUFTLENBQUMsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLElBQUEsa0JBQVUsRUFBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxPQUFPLEVBQUUsTUFBTSxjQUFjLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ2hHLE9BQU87SUFDWCxDQUFDO0lBQ0QsSUFBSSxJQUFJLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQ2xCLE1BQU0sSUFBSSxLQUFLLENBQUMsOENBQThDLElBQUksQ0FBQyxNQUFNLGlCQUFpQixLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBQ3ZHLENBQUM7SUFDRCxNQUFNLFlBQVksQ0FBQyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsTUFBTSxjQUFjLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0FBQ3pFLENBQUM7QUF6QkQsb0JBeUJDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGZzIGZyb20gXCJmcy9wcm9taXNlc1wiO1xuaW1wb3J0IG9zIGZyb20gXCJvc1wiO1xuaW1wb3J0IHBhdGggZnJvbSBcInBhdGhcIjtcbmltcG9ydCB7IERFRkFVTFRfV0VCSE9PS19GSUxFX1BBVEgsIHBhcnNlV2ViaG9va0ZpbGUgfSBmcm9tIFwiLi4vbm90aWZpY2F0aW9ucy9kaXNjb3JkXCI7XG5pbXBvcnQgeyBub3JtYWxpemVLZXlzLCByZWFkUmVwb0tleXMsIHN1bW1hcml6ZUtleSB9IGZyb20gXCIuL2F1dGhvcml6ZWRLZXlzXCI7XG5pbXBvcnQgeyBzb3VyY2VLZXlQYXRoLCBzb3VyY2VSZXBvUGF0aCB9IGZyb20gXCIuL3NvdXJjZXNcIjtcbmltcG9ydCB7IGRlcml2ZVJldm9rZUtleSwgUkVWT0tFX0tFWV9MQUJFTCwgcmV2b2tlUmVwb1VSTCB9IGZyb20gXCIuL3Jldm9rZVNvdXJjZVwiO1xuaW1wb3J0IHsgcmV2b2tlZEtleXNJblJlcG8gfSBmcm9tIFwiLi91bnJldm9rZVwiO1xuaW1wb3J0IHsgZXhwYW5kSG9tZSB9IGZyb20gXCIuLi9oZWxwZXJzL3BhdGhzXCI7XG5pbXBvcnQgeyBzcGF3blByb21pc2UgfSBmcm9tIFwiLi4vaGVscGVycy9zcGF3blwiO1xuaW1wb3J0IHsgcmVhZFJlbW90ZUZpbGUsIHJlbW90ZUNvbW1hbmRFeGlzdHMsIHJ1bk92ZXJTU0gsIFNVRE9fUFJFQU1CTEUsIHdyaXRlUmVtb3RlRmlsZSB9IGZyb20gXCIuLi9oZWxwZXJzL3JlbW90ZVNTSFwiO1xuXG5jb25zdCBTRVJWSUNFX1NPVVJDRSA9IHBhdGguam9pbihfX2Rpcm5hbWUsIFwiZGFlbW9uXCIsIFwicG9ydHNlY3VyZS5zZXJ2aWNlXCIpO1xuLy8gVGhlIGRhZW1vbiBydW5zIGZyb20gYSBjaGVja291dCBvZiB0aGlzIHJlcG8gb24gdGhlIGhvc3QsIHJhdGhlciB0aGFuIGZyb20gYSBjb3B5IHdlIHVwbG9hZCwgc29cbi8vIGEgaG9zdCB1cGRhdGVzIGl0c2VsZiBmcm9tIGdpdGh1YiB0aGUgc2FtZSB3YXkgYW55dGhpbmcgZWxzZSBkb2VzLiBzbGlmdHV0aWxzIGlzIHB1YmxpYywgc28gdGhpc1xuLy8gbmVlZHMgbm8ga2V5LlxuY29uc3QgU0xJRlRVVElMU19VUkwgPSBcImh0dHBzOi8vZ2l0aHViLmNvbS9zbGlmdGlzdC9zbGlmdHV0aWxzLmdpdFwiO1xuY29uc3QgUkVNT1RFX0NIRUNLT1VUX1BBVEggPSBcIi9vcHQvcG9ydHNlY3VyZS9zbGlmdHV0aWxzXCI7XG5jb25zdCBSRU1PVEVfU0VSVklDRV9QQVRIID0gXCIvZXRjL3N5c3RlbWQvc3lzdGVtL3BvcnRzZWN1cmUuc2VydmljZVwiO1xuY29uc3QgUkVNT1RFX0NPTkZJR19QQVRIID0gXCIvZXRjL3BvcnRzZWN1cmUvZGFlbW9uLmpzb25cIjtcbmNvbnN0IFJPT1RfQVVUSE9SSVpFRF9LRVlTID0gXCIvcm9vdC8uc3NoL2F1dGhvcml6ZWRfa2V5c1wiO1xuY29uc3QgU0VSVklDRV9OQU1FID0gXCJwb3J0c2VjdXJlXCI7XG5jb25zdCBNQVhfRVJST1JfQk9EWV9MRU5HVEggPSA1MDA7XG5jb25zdCBWRVJCUyA9IFtcImFkZFwiLCBcInJlbW92ZVwiLCBcImxpc3RcIiwgXCJ1cGRhdGVcIl07XG4vLyBUaGUgcmVwbyB1cmwgaXMgb3B0aW9uYWwsIGFuZCBkZWZhdWx0cyB0byB0aGUgcmVwbyB0aGUgY29tbWFuZCBpcyBydW4gZnJvbS5cbmNvbnN0IFVTQUdFID0gYFVzYWdlOlxuICB5YXJuIHNlY3VyZXNzaCA8aG9zdD4gYWRkIDxyZXBvLXByaXZhdGUta2V5PiBbcmVwby11cmxdXG4gIHlhcm4gc2VjdXJlc3NoIDxob3N0PiByZW1vdmUgW3JlcG8tdXJsXVxuICB5YXJuIHNlY3VyZXNzaCA8aG9zdD4gbGlzdFxuICB5YXJuIHNlY3VyZXNzaCA8aG9zdD4gdXBkYXRlYDtcblxuYXN5bmMgZnVuY3Rpb24gcGF0aEV4aXN0cyhmaWxlUGF0aDogc3RyaW5nKSB7XG4gICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgZnMuYWNjZXNzKGZpbGVQYXRoKTtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxufVxuXG5hc3luYyBmdW5jdGlvbiBydW5Mb2NhbChjb25maWc6IHsgY29tbWFuZDogc3RyaW5nOyBhcmdzOiBzdHJpbmdbXTsgY3dkPzogc3RyaW5nOyBhbGxvd0ZhaWx1cmU/OiBib29sZWFuIH0pIHtcbiAgICBsZXQgeyBjb21tYW5kLCBhcmdzLCBjd2QsIGFsbG93RmFpbHVyZSB9ID0gY29uZmlnO1xuICAgIGxldCByZXN1bHQgPSBhd2FpdCBzcGF3blByb21pc2UoeyBjb21tYW5kLCBhcmdzLCBjd2QgfSk7XG4gICAgaWYgKHJlc3VsdC5lcnJvcikge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkICR7Y29tbWFuZH0gdG8gcnVuLCBmYWlsZWQgd2l0aCAke3Jlc3VsdC5lcnJvci5tZXNzYWdlfWApO1xuICAgIH1cbiAgICBpZiAocmVzdWx0LnN0YXR1cyAhPT0gMCAmJiAhYWxsb3dGYWlsdXJlKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIGBFeHBlY3RlZCAke2NvbW1hbmR9ICR7YXJncy5qb2luKFwiIFwiKX0gdG8gZXhpdCAwLCB3YXMgJHtyZXN1bHQuc3RhdHVzfS4gYFxuICAgICAgICAgICAgKyBgJHsocmVzdWx0LnN0ZG91dCArIHJlc3VsdC5zdGRlcnIpLnRyaW0oKS5zbGljZSgwLCBNQVhfRVJST1JfQk9EWV9MRU5HVEgpfWBcbiAgICAgICAgKTtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbn1cblxuLyoqIEEgcHJpdmF0ZSBrZXkgY2Fubm90IGF1dGhlbnRpY2F0ZSBhbiBodHRwcyByZW1vdGUsIHNvIGdpdGh1YiB1cmxzIGFyZSBjb252ZXJ0ZWQgdG8gdGhlIHNzaCBmb3JtXG4gICAgdGhlIGtleSBjYW4gYWN0dWFsbHkgYmUgdXNlZCB3aXRoLiAqL1xuZnVuY3Rpb24gbm9ybWFsaXplUmVwb1VSTChyZXBvVVJMOiBzdHJpbmcpIHtcbiAgICBsZXQgaHR0cHNNYXRjaCA9IHJlcG9VUkwubWF0Y2goL15odHRwcz86XFwvXFwvKFteL10rKVxcLyguKz8pKD86XFwuZ2l0KT9cXC8/JC8pO1xuICAgIGlmICghaHR0cHNNYXRjaCkge1xuICAgICAgICByZXR1cm4gcmVwb1VSTDtcbiAgICB9XG4gICAgbGV0IFssIGhvc3QsIHJlcG9QYXRoXSA9IGh0dHBzTWF0Y2g7XG4gICAgcmV0dXJuIGBnaXRAJHtob3N0fToke3JlcG9QYXRofS5naXRgO1xufVxuXG5hc3luYyBmdW5jdGlvbiBnaXRXaXRoS2V5KGNvbmZpZzogeyBrZXlQYXRoOiBzdHJpbmc7IGFyZ3M6IHN0cmluZ1tdOyBjd2Q/OiBzdHJpbmc7IGFsbG93RmFpbHVyZT86IGJvb2xlYW4gfSkge1xuICAgIGxldCB7IGtleVBhdGgsIGFyZ3MsIGN3ZCwgYWxsb3dGYWlsdXJlIH0gPSBjb25maWc7XG4gICAgLy8gY29yZS5zc2hDb21tYW5kIGtlZXBzIGtleSBzZWxlY3Rpb24gd2l0aCB0aGUgY29tbWFuZCBpbnN0ZWFkIG9mIGluIHRoZSBlbnZpcm9ubWVudC5cbiAgICBsZXQgc3NoQ29tbWFuZCA9IGBzc2ggLWkgJHtrZXlQYXRofSAtbyBJZGVudGl0aWVzT25seT15ZXMgLW8gQmF0Y2hNb2RlPXllcyAtbyBTdHJpY3RIb3N0S2V5Q2hlY2tpbmc9YWNjZXB0LW5ld2A7XG4gICAgbGV0IHJlc3VsdCA9IGF3YWl0IHNwYXduUHJvbWlzZSh7IGNvbW1hbmQ6IFwiZ2l0XCIsIGFyZ3M6IFtcIi1jXCIsIGBjb3JlLnNzaENvbW1hbmQ9JHtzc2hDb21tYW5kfWAsIC4uLmFyZ3NdLCBjd2QgfSk7XG4gICAgaWYgKHJlc3VsdC5lcnJvcikge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGdpdCB0byBydW4sIGZhaWxlZCB3aXRoICR7cmVzdWx0LmVycm9yLm1lc3NhZ2V9YCk7XG4gICAgfVxuICAgIGlmIChyZXN1bHQuc3RhdHVzICE9PSAwICYmICFhbGxvd0ZhaWx1cmUpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgYEV4cGVjdGVkIGdpdCAke2FyZ3Muam9pbihcIiBcIil9IHRvIGV4aXQgMCwgd2FzICR7cmVzdWx0LnN0YXR1c30uIGBcbiAgICAgICAgICAgICsgYCR7KHJlc3VsdC5zdGRvdXQgKyByZXN1bHQuc3RkZXJyKS50cmltKCkuc2xpY2UoMCwgTUFYX0VSUk9SX0JPRFlfTEVOR1RIKX1gXG4gICAgICAgICk7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG59XG5cbi8qKiBBc2tzIHNzaCB3aGljaCBrZXkgaXQgYWN0dWFsbHkgYXV0aGVudGljYXRlZCB3aXRoLiBUaGlzIGlzIHRoZSBrZXkgdGhhdCBtdXN0IHN1cnZpdmUgdGhlXG4gICAgZGFlbW9uIHRha2luZyBvdmVyIGF1dGhvcml6ZWRfa2V5cywgb3RoZXJ3aXNlIGEgZGVwbG95IGxvY2tzIHVzIG91dC4gKi9cbmFzeW5jIGZ1bmN0aW9uIGZpbmRBdXRoZW50aWNhdGluZ0ZpbmdlcnByaW50KGhvc3Q6IHN0cmluZykge1xuICAgIGxldCByZXN1bHQgPSBhd2FpdCBzcGF3blByb21pc2Uoe1xuICAgICAgICBjb21tYW5kOiBcInNzaFwiLFxuICAgICAgICBhcmdzOiBbXCItdlwiLCBcIi1vXCIsIFwiQmF0Y2hNb2RlPXllc1wiLCBcIi1vXCIsIFwiQ29ubmVjdFRpbWVvdXQ9MTBcIiwgaG9zdCwgXCJ0cnVlXCJdLFxuICAgIH0pO1xuICAgIGxldCB2ZXJib3NlT3V0cHV0ID0gcmVzdWx0LnN0ZGVyciB8fCBcIlwiO1xuICAgIGlmIChyZXN1bHQuc3RhdHVzICE9PSAwKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgdG8gc3NoIGludG8gJHtob3N0fSwgZmFpbGVkLiAke3ZlcmJvc2VPdXRwdXQuc2xpY2UoLU1BWF9FUlJPUl9CT0RZX0xFTkdUSCl9YCk7XG4gICAgfVxuICAgIGxldCBhY2NlcHRlZE1hdGNoID0gdmVyYm9zZU91dHB1dC5tYXRjaCgvU2VydmVyIGFjY2VwdHMga2V5Oi4qPyhTSEEyNTY6W0EtWmEtejAtOSsvPV0rKS8pO1xuICAgIGlmICghYWNjZXB0ZWRNYXRjaCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBgRXhwZWN0ZWQgJHtob3N0fSB0byBhY2NlcHQgYSBwdWJsaWMga2V5LCBidXQgdGhlIHNlc3Npb24gZGlkIG5vdCBhdXRoZW50aWNhdGUgd2l0aCBvbmUuYFxuICAgICAgICAgICAgKyBgIHBvcnRzZWN1cmUgZGlzYWJsZXMgcGFzc3dvcmQgbG9naW4sIHNvIGtleSBiYXNlZCBhY2Nlc3MgaGFzIHRvIHdvcmsgZmlyc3QuYFxuICAgICAgICApO1xuICAgIH1cbiAgICByZXR1cm4gYWNjZXB0ZWRNYXRjaFsxXTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZmluZ2VycHJpbnRLZXlzKGtleXM6IHN0cmluZ1tdKSB7XG4gICAgaWYgKCFrZXlzLmxlbmd0aCkge1xuICAgICAgICByZXR1cm4gW107XG4gICAgfVxuICAgIGxldCB0ZW1wb3JhcnlEaXJlY3RvcnkgPSBhd2FpdCBmcy5ta2R0ZW1wKHBhdGguam9pbihvcy50bXBkaXIoKSwgXCJwb3J0c2VjdXJlLWtleXMtXCIpKTtcbiAgICBsZXQga2V5c1BhdGggPSBwYXRoLmpvaW4odGVtcG9yYXJ5RGlyZWN0b3J5LCBcImF1dGhvcml6ZWRfa2V5c1wiKTtcbiAgICBhd2FpdCBmcy53cml0ZUZpbGUoa2V5c1BhdGgsIGAke2tleXMuam9pbihcIlxcblwiKX1cXG5gKTtcbiAgICBsZXQgcmVzdWx0ID0gYXdhaXQgcnVuTG9jYWwoeyBjb21tYW5kOiBcInNzaC1rZXlnZW5cIiwgYXJnczogW1wiLWxmXCIsIGtleXNQYXRoXSwgYWxsb3dGYWlsdXJlOiB0cnVlIH0pO1xuICAgIGF3YWl0IGZzLnJtKHRlbXBvcmFyeURpcmVjdG9yeSwgeyByZWN1cnNpdmU6IHRydWUsIGZvcmNlOiB0cnVlIH0pO1xuICAgIGxldCBmaW5nZXJwcmludHM6IHN0cmluZ1tdID0gW107XG4gICAgZm9yIChsZXQgbGluZSBvZiByZXN1bHQuc3Rkb3V0LnNwbGl0KFwiXFxuXCIpKSB7XG4gICAgICAgIGxldCBtYXRjaCA9IGxpbmUubWF0Y2goLyhTSEEyNTY6W0EtWmEtejAtOSsvPV0rKS8pO1xuICAgICAgICBpZiAobWF0Y2gpIHtcbiAgICAgICAgICAgIGZpbmdlcnByaW50cy5wdXNoKG1hdGNoWzFdKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gZmluZ2VycHJpbnRzO1xufVxuXG5hc3luYyBmdW5jdGlvbiBjbG9uZVJlcG9Gb3JJbnNwZWN0aW9uKGNvbmZpZzogeyByZXBvVVJMOiBzdHJpbmc7IGtleVBhdGg6IHN0cmluZyB9KSB7XG4gICAgbGV0IHsgcmVwb1VSTCwga2V5UGF0aCB9ID0gY29uZmlnO1xuICAgIGxldCB0ZW1wb3JhcnlEaXJlY3RvcnkgPSBhd2FpdCBmcy5ta2R0ZW1wKHBhdGguam9pbihvcy50bXBkaXIoKSwgXCJwb3J0c2VjdXJlLXJlcG8tXCIpKTtcbiAgICBsZXQgcmVwb1BhdGggPSBwYXRoLmpvaW4odGVtcG9yYXJ5RGlyZWN0b3J5LCBcInJlcG9cIik7XG4gICAgYXdhaXQgZ2l0V2l0aEtleSh7IGtleVBhdGgsIGFyZ3M6IFtcImNsb25lXCIsIFwiLS1kZXB0aFwiLCBcIjFcIiwgcmVwb1VSTCwgcmVwb1BhdGhdIH0pO1xuICAgIHJldHVybiByZXBvUGF0aDtcbn1cblxuLyoqIFdpdGggbm8gcmVwbyB1cmwgZ2l2ZW4sIHRoZSByZXBvIHdlIGFyZSBzdGFuZGluZyBpbiBpcyB1c2VkLiBJdCBoYXMgdG8gYWN0dWFsbHkgaG9sZCBrZXlzXG4gICAgYmVmb3JlIHdlIGhhbmQgaXQgdG8gYSBob3N0LCBzbyBhIHN0cmF5IHdvcmtpbmcgZGlyZWN0b3J5IGNhbm5vdCBiZSBkZXBsb3llZCBieSBhY2NpZGVudC4gKi9cbmFzeW5jIGZ1bmN0aW9uIHJlc29sdmVSZXBvVVJMKHBhc3NlZFVSTDogc3RyaW5nIHwgdW5kZWZpbmVkKSB7XG4gICAgaWYgKHBhc3NlZFVSTCkge1xuICAgICAgICByZXR1cm4gbm9ybWFsaXplUmVwb1VSTChwYXNzZWRVUkwpO1xuICAgIH1cbiAgICBsZXQgdG9wTGV2ZWwgPSBhd2FpdCBydW5Mb2NhbCh7IGNvbW1hbmQ6IFwiZ2l0XCIsIGFyZ3M6IFtcInJldi1wYXJzZVwiLCBcIi0tc2hvdy10b3BsZXZlbFwiXSwgYWxsb3dGYWlsdXJlOiB0cnVlIH0pO1xuICAgIGlmICh0b3BMZXZlbC5zdGF0dXMgIT09IDApIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBhIHJlcG8gdXJsLCBvciB0aGUgY3VycmVudCBkaXJlY3RvcnkgdG8gYmUgaW5zaWRlIGEgZ2l0IHJlcG8sIGl0IGlzIG5vdC5cXG4ke1VTQUdFfWApO1xuICAgIH1cbiAgICBsZXQgcmVwb1BhdGggPSB0b3BMZXZlbC5zdGRvdXQudHJpbSgpO1xuICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IHJlYWRSZXBvS2V5cyhyZXBvUGF0aCk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBgRXhwZWN0ZWQgYSByZXBvIHVybCwgb3IgdGhlIGN1cnJlbnQgcmVwbyAoJHtyZXBvUGF0aH0pIHRvIGhvbGQga2V5cywgaXQgZG9lcyBub3QuXFxuJHtlfVxcbiR7VVNBR0V9YFxuICAgICAgICApO1xuICAgIH1cbiAgICBsZXQgb3JpZ2luID0gYXdhaXQgcnVuTG9jYWwoe1xuICAgICAgICBjb21tYW5kOiBcImdpdFwiLFxuICAgICAgICBhcmdzOiBbXCJyZW1vdGVcIiwgXCJnZXQtdXJsXCIsIFwib3JpZ2luXCJdLFxuICAgICAgICBjd2Q6IHJlcG9QYXRoLFxuICAgICAgICBhbGxvd0ZhaWx1cmU6IHRydWUsXG4gICAgfSk7XG4gICAgaWYgKG9yaWdpbi5zdGF0dXMgIT09IDAgfHwgIW9yaWdpbi5zdGRvdXQudHJpbSgpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIGBFeHBlY3RlZCB0aGUgY3VycmVudCByZXBvICgke3JlcG9QYXRofSkgdG8gaGF2ZSBhbiBvcmlnaW4gcmVtb3RlLCBpdCBoYXMgbm9uZS5gXG4gICAgICAgICAgICArIGAgVGhlIGhvc3QgY2xvbmVzIHRoZSBzb3VyY2UgaXRzZWxmLCBzbyBhIGxvY2FsIHBhdGggaXMgbm8gdXNlIHRvIGl0LmBcbiAgICAgICAgKTtcbiAgICB9XG4gICAgbGV0IHJlcG9VUkwgPSBub3JtYWxpemVSZXBvVVJMKG9yaWdpbi5zdGRvdXQudHJpbSgpKTtcbiAgICBjb25zb2xlLmxvZyhgTm8gcmVwbyB1cmwgZ2l2ZW4sIHVzaW5nIHRoZSBjdXJyZW50IHJlcG86ICR7cmVwb1VSTH1gKTtcbiAgICByZXR1cm4gcmVwb1VSTDtcbn1cblxuLyoqIEV2ZXJ5IHNvdXJjZSdzIHJldm9rZSByZXBvIGhhcyB0byBleGlzdCBhbmQgaG9sZCBhdCBsZWFzdCBvbmUgY29tbWl0LCBvciB0aGUgaG9zdHMgdXNpbmcgaXRcbiAgICBjYW5ub3QgcmVjb3JkIGEgcmV2b2NhdGlvbi4gQ2hlY2tlZCB3aXRoIHRoaXMgbWFjaGluZSdzIG93biBnaXQgY3JlZGVudGlhbHMsIHNpbmNlIHVwZGF0ZSBpc1xuICAgIG5vdCBnaXZlbiBhbnkgZGVwbG95IGtleSwgYW5kIGFuIGVtcHR5IHJlcG8gaXMgaW5pdGlhbGlzZWQgcmF0aGVyIHRoYW4gbWVyZWx5IGNvbXBsYWluZWQgYWJvdXQuXG5cbiAgICBBIGhvc3QgdGhhdCBjYW5ub3Qgd3JpdGUgYSByZXZvY2F0aW9uIHNpbGVudGx5IGtlZXBzIGFjY2VwdGluZyBhIGtleSBpdCBqdXN0IHNhdyBiZWluZyBtaXN1c2VkLFxuICAgIHdoaWNoIGlzIHRoZSBvbmUgZmFpbHVyZSB0aGlzIHdob2xlIHRoaW5nIGV4aXN0cyB0byBwcmV2ZW50LCBzbyBpdCBpcyBjaGVja2VkIG9uIGV2ZXJ5IGRlcGxveVxuICAgIGFuZCBub3Qgb25seSB3aGVuIGEgc291cmNlIGlzIGZpcnN0IGFkZGVkLiAqL1xuYXN5bmMgZnVuY3Rpb24gZW5zdXJlUmV2b2tlUmVwb3NFeGlzdChyZXBvU291cmNlczogc3RyaW5nW10pIHtcbiAgICBmb3IgKGxldCByZXBvVVJMIG9mIHJlcG9Tb3VyY2VzKSB7XG4gICAgICAgIGxldCByZXZva2VVUkwgPSByZXZva2VSZXBvVVJMKHJlcG9VUkwpO1xuICAgICAgICBsZXQgdGVtcG9yYXJ5RGlyZWN0b3J5ID0gYXdhaXQgZnMubWtkdGVtcChwYXRoLmpvaW4ob3MudG1wZGlyKCksIFwicG9ydHNlY3VyZS1yZXZva2UtXCIpKTtcbiAgICAgICAgbGV0IGNoZWNrb3V0UGF0aCA9IHBhdGguam9pbih0ZW1wb3JhcnlEaXJlY3RvcnksIFwicmVwb1wiKTtcbiAgICAgICAgbGV0IGNsb25lID0gYXdhaXQgcnVuTG9jYWwoeyBjb21tYW5kOiBcImdpdFwiLCBhcmdzOiBbXCJjbG9uZVwiLCByZXZva2VVUkwsIGNoZWNrb3V0UGF0aF0sIGFsbG93RmFpbHVyZTogdHJ1ZSB9KTtcbiAgICAgICAgaWYgKGNsb25lLnN0YXR1cyAhPT0gMCkge1xuICAgICAgICAgICAgYXdhaXQgZnMucm0odGVtcG9yYXJ5RGlyZWN0b3J5LCB7IHJlY3Vyc2l2ZTogdHJ1ZSwgZm9yY2U6IHRydWUgfSk7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAgICAgYEV4cGVjdGVkICR7cmV2b2tlVVJMfSB0byBleGlzdCwgaXQgZG9lcyBub3QsIHNvICR7cmVwb1VSTH0gaGFzIG5vd2hlcmUgdG8gcmVjb3JkIGFgXG4gICAgICAgICAgICAgICAgKyBgIHJldm9jYXRpb24uXFxuQ3JlYXRlIGl0LCB0aGVuIHJ1biBcInlhcm4gc2VjdXJlc3NoIDxob3N0PiBhZGRcIiBmb3IgdGhhdCBzb3VyY2UgdG9gXG4gICAgICAgICAgICAgICAgKyBgIHJlZ2lzdGVyIGl0cyBkZXBsb3kga2V5LlxcbiR7KGNsb25lLnN0ZG91dCArIGNsb25lLnN0ZGVycikudHJpbSgpLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCl9YFxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICBsZXQgaGVhZCA9IGF3YWl0IHJ1bkxvY2FsKHsgY29tbWFuZDogXCJnaXRcIiwgYXJnczogW1wiLUNcIiwgY2hlY2tvdXRQYXRoLCBcInJldi1wYXJzZVwiLCBcIkhFQURcIl0sIGFsbG93RmFpbHVyZTogdHJ1ZSB9KTtcbiAgICAgICAgaWYgKGhlYWQuc3RhdHVzICE9PSAwKSB7XG4gICAgICAgICAgICBjb25zb2xlLmxvZyhgJHtyZXZva2VVUkx9IGlzIGVtcHR5LCBnaXZpbmcgaXQgYSBmaXJzdCBjb21taXRgKTtcbiAgICAgICAgICAgIGF3YWl0IGZzLndyaXRlRmlsZShwYXRoLmpvaW4oY2hlY2tvdXRQYXRoLCBcIlJFQURNRS5tZFwiKSxcbiAgICAgICAgICAgICAgICBgIyByZXZva2VkIGtleXNcXG5cXG5Xcml0dGVuIGJ5IHBvcnRzZWN1cmUuIEVhY2ggZmlsZSB1bmRlciByZXZvY2F0aW9ucy8gaXMgb25lIGtleSB0aGF0IHdhcyB1c2VkIGZyb20gYW5cXG5gXG4gICAgICAgICAgICAgICAgKyBgYWRkcmVzcyBpdCBpcyBub3QgYWxsb3dlZCBmcm9tLCBhbmQgaXMgbm8gbG9uZ2VyIGFjY2VwdGVkIGFueXdoZXJlLlxcbmApO1xuICAgICAgICAgICAgZm9yIChsZXQgYXJncyBvZiBbXG4gICAgICAgICAgICAgICAgW1wiLUNcIiwgY2hlY2tvdXRQYXRoLCBcImFkZFwiLCBcIi1BXCJdLFxuICAgICAgICAgICAgICAgIFtcIi1DXCIsIGNoZWNrb3V0UGF0aCwgXCItY1wiLCBcInVzZXIuZW1haWw9cG9ydHNlY3VyZUBsb2NhbGhvc3RcIiwgXCItY1wiLCBcInVzZXIubmFtZT1wb3J0c2VjdXJlXCIsIFwiY29tbWl0XCIsIFwiLW1cIiwgXCJpbml0aWFsaXNlIHJldm9rZSByZXBvXCJdLFxuICAgICAgICAgICAgICAgIFtcIi1DXCIsIGNoZWNrb3V0UGF0aCwgXCJwdXNoXCIsIFwib3JpZ2luXCIsIFwiSEVBRFwiXSxcbiAgICAgICAgICAgIF0pIHtcbiAgICAgICAgICAgICAgICBhd2FpdCBydW5Mb2NhbCh7IGNvbW1hbmQ6IFwiZ2l0XCIsIGFyZ3MgfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgYXdhaXQgZnMucm0odGVtcG9yYXJ5RGlyZWN0b3J5LCB7IHJlY3Vyc2l2ZTogdHJ1ZSwgZm9yY2U6IHRydWUgfSk7XG4gICAgICAgIGNvbnNvbGUubG9nKGAke3Jldm9rZVVSTH0gaXMgcmVhZHlgKTtcbiAgICB9XG59XG5cbi8qKiBUaGUgcmV2b2tlIHJlcG8gaGFzIHRvIGV4aXN0IGFuZCBiZSB3cml0YWJsZSBiZWZvcmUgYSBob3N0IGlzIHNldCB1cCwgYmVjYXVzZSBhIGhvc3QgdGhhdFxuICAgIGNhbm5vdCB3cml0ZSBhIHJldm9jYXRpb24gY2Fubm90IHJldm9rZSBhIGtleSB0aGF0IGlzIGJlaW5nIG1pc3VzZWQuIFRoZSBrZXkgZm9yIGl0IGlzIGRlcml2ZWRcbiAgICBmcm9tIHRoZSBzb3VyY2Uncywgc2luY2UgZ2l0aHViIHdpbGwgbm90IHRha2Ugb25lIHB1YmxpYyBrZXkgb24gdHdvIHJlcG9zLiAqL1xuYXN5bmMgZnVuY3Rpb24gZW5zdXJlUmV2b2tlUmVwbyhjb25maWc6IHsga2V5UGF0aDogc3RyaW5nOyByZXBvVVJMOiBzdHJpbmcgfSkge1xuICAgIGxldCB7IGtleVBhdGgsIHJlcG9VUkwgfSA9IGNvbmZpZztcbiAgICBsZXQgcmV2b2tlVVJMID0gcmV2b2tlUmVwb1VSTChyZXBvVVJMKTtcbiAgICBsZXQgZGVyaXZlZCA9IGRlcml2ZVJldm9rZUtleShhd2FpdCBmcy5yZWFkRmlsZShrZXlQYXRoLCBcInV0ZjhcIikpO1xuXG4gICAgbGV0IHRlbXBvcmFyeURpcmVjdG9yeSA9IGF3YWl0IGZzLm1rZHRlbXAocGF0aC5qb2luKG9zLnRtcGRpcigpLCBcInBvcnRzZWN1cmUtcmV2b2tlLVwiKSk7XG4gICAgbGV0IGRlcml2ZWRLZXlQYXRoID0gcGF0aC5qb2luKHRlbXBvcmFyeURpcmVjdG9yeSwgXCJrZXlcIik7XG4gICAgYXdhaXQgZnMud3JpdGVGaWxlKGRlcml2ZWRLZXlQYXRoLCBkZXJpdmVkLnByaXZhdGVLZXlGaWxlLCB7IG1vZGU6IDBvNjAwIH0pO1xuICAgIGxldCBjaGVja291dFBhdGggPSBwYXRoLmpvaW4odGVtcG9yYXJ5RGlyZWN0b3J5LCBcInJlcG9cIik7XG5cbiAgICBsZXQgZXhwbGFpbiA9IChwcm9ibGVtOiBzdHJpbmcpID0+IG5ldyBFcnJvcihcbiAgICAgICAgYCR7cHJvYmxlbX1cXG5gXG4gICAgICAgICsgYENyZWF0ZSAke3Jldm9rZVVSTH0gYW5kIGFkZCB0aGlzIGFzIGEgZGVwbG95IGtleSBXSVRIIFdSSVRFIEFDQ0VTUzpcXG5gXG4gICAgICAgICsgYCAgJHtkZXJpdmVkLnB1YmxpY0tleX0gJHtSRVZPS0VfS0VZX0xBQkVMfVxcbmBcbiAgICAgICAgKyBgSXQgaGFzIHRvIGJlIHRoaXMga2V5OiBpdCBpcyBkZXJpdmVkIGZyb20gJHtrZXlQYXRofSwgYW5kIGdpdGh1YiB3aWxsIG5vdCBhY2NlcHQgdGhlIHNhbWVgXG4gICAgICAgICsgYCBwdWJsaWMga2V5IG9uIHR3byByZXBvc2l0b3JpZXMuYFxuICAgICk7XG5cbiAgICBjb25zb2xlLmxvZyhgQ2hlY2tpbmcgJHtyZXZva2VVUkx9YCk7XG4gICAgbGV0IGNsb25lID0gYXdhaXQgZ2l0V2l0aEtleSh7IGtleVBhdGg6IGRlcml2ZWRLZXlQYXRoLCBhcmdzOiBbXCJjbG9uZVwiLCByZXZva2VVUkwsIGNoZWNrb3V0UGF0aF0sIGFsbG93RmFpbHVyZTogdHJ1ZSB9KTtcbiAgICBpZiAoY2xvbmUuc3RhdHVzICE9PSAwKSB7XG4gICAgICAgIHRocm93IGV4cGxhaW4oYEV4cGVjdGVkICR7cmV2b2tlVVJMfSB0byBiZSByZWFkYWJsZSB3aXRoIHRoZSBkZXJpdmVkIGtleSwgaXQgaXMgbm90LmApO1xuICAgIH1cblxuICAgIC8vIEFuIGVtcHR5IHJlcG8gaGFzIG5vIGJyYW5jaCBmb3IgdGhlIGRhZW1vbiB0byBjbG9uZSwgc28gaXQgZ2V0cyBpdHMgZmlyc3QgY29tbWl0IGhlcmUuIFRoYXRcbiAgICAvLyBkb3VibGVzIGFzIHRoZSBwcm9vZiB0aGF0IHdlIGNhbiB3cml0ZSB0byBpdC5cbiAgICBsZXQgaGVhZCA9IGF3YWl0IGdpdFdpdGhLZXkoeyBrZXlQYXRoOiBkZXJpdmVkS2V5UGF0aCwgYXJnczogW1wicmV2LXBhcnNlXCIsIFwiSEVBRFwiXSwgY3dkOiBjaGVja291dFBhdGgsIGFsbG93RmFpbHVyZTogdHJ1ZSB9KTtcbiAgICBpZiAoaGVhZC5zdGF0dXMgIT09IDApIHtcbiAgICAgICAgYXdhaXQgZnMud3JpdGVGaWxlKHBhdGguam9pbihjaGVja291dFBhdGgsIFwiUkVBRE1FLm1kXCIpLFxuICAgICAgICAgICAgYCMgcmV2b2tlZCBrZXlzXFxuXFxuV3JpdHRlbiBieSBwb3J0c2VjdXJlLiBFYWNoIGZpbGUgdW5kZXIgcmV2b2NhdGlvbnMvIGlzIG9uZSBrZXkgdGhhdCB3YXMgdXNlZCBmcm9tIGFuXFxuYFxuICAgICAgICAgICAgKyBgYWRkcmVzcyBpdCBpcyBub3QgYWxsb3dlZCBmcm9tLCBhbmQgaXMgbm8gbG9uZ2VyIGFjY2VwdGVkIGFueXdoZXJlLlxcbmApO1xuICAgICAgICBmb3IgKGxldCBhcmdzIG9mIFtcbiAgICAgICAgICAgIFtcImFkZFwiLCBcIi1BXCJdLFxuICAgICAgICAgICAgW1wiLWNcIiwgXCJ1c2VyLmVtYWlsPXBvcnRzZWN1cmVAbG9jYWxob3N0XCIsIFwiLWNcIiwgXCJ1c2VyLm5hbWU9cG9ydHNlY3VyZVwiLCBcImNvbW1pdFwiLCBcIi1tXCIsIFwiaW5pdGlhbGlzZSByZXZva2UgcmVwb1wiXSxcbiAgICAgICAgXSkge1xuICAgICAgICAgICAgYXdhaXQgZ2l0V2l0aEtleSh7IGtleVBhdGg6IGRlcml2ZWRLZXlQYXRoLCBhcmdzLCBjd2Q6IGNoZWNrb3V0UGF0aCB9KTtcbiAgICAgICAgfVxuICAgICAgICBsZXQgcHVzaCA9IGF3YWl0IGdpdFdpdGhLZXkoeyBrZXlQYXRoOiBkZXJpdmVkS2V5UGF0aCwgYXJnczogW1wicHVzaFwiLCBcIm9yaWdpblwiLCBcIkhFQURcIl0sIGN3ZDogY2hlY2tvdXRQYXRoLCBhbGxvd0ZhaWx1cmU6IHRydWUgfSk7XG4gICAgICAgIGlmIChwdXNoLnN0YXR1cyAhPT0gMCkge1xuICAgICAgICAgICAgdGhyb3cgZXhwbGFpbihgRXhwZWN0ZWQgd3JpdGUgYWNjZXNzIHRvICR7cmV2b2tlVVJMfSwgdGhlIGZpcnN0IHB1c2ggd2FzIHJlZnVzZWQuXFxuJHsocHVzaC5zdGRvdXQgKyBwdXNoLnN0ZGVycikudHJpbSgpLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCl9YCk7XG4gICAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgICBsZXQgZHJ5UnVuID0gYXdhaXQgZ2l0V2l0aEtleSh7IGtleVBhdGg6IGRlcml2ZWRLZXlQYXRoLCBhcmdzOiBbXCJwdXNoXCIsIFwiLS1kcnktcnVuXCIsIFwib3JpZ2luXCIsIFwiSEVBRFwiXSwgY3dkOiBjaGVja291dFBhdGgsIGFsbG93RmFpbHVyZTogdHJ1ZSB9KTtcbiAgICAgICAgaWYgKGRyeVJ1bi5zdGF0dXMgIT09IDApIHtcbiAgICAgICAgICAgIHRocm93IGV4cGxhaW4oYEV4cGVjdGVkIHdyaXRlIGFjY2VzcyB0byAke3Jldm9rZVVSTH0sIGEgZHJ5IHJ1biBwdXNoIHdhcyByZWZ1c2VkLlxcbiR7KGRyeVJ1bi5zdGRvdXQgKyBkcnlSdW4uc3RkZXJyKS50cmltKCkuc2xpY2UoMCwgTUFYX0VSUk9SX0JPRFlfTEVOR1RIKX1gKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGF3YWl0IGZzLnJtKHRlbXBvcmFyeURpcmVjdG9yeSwgeyByZWN1cnNpdmU6IHRydWUsIGZvcmNlOiB0cnVlIH0pO1xuICAgIGNvbnNvbGUubG9nKGAke3Jldm9rZVVSTH0gaXMgd3JpdGFibGVgKTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gcmVhZFJlbW90ZUNvbmZpZyhob3N0OiBzdHJpbmcpIHtcbiAgICBsZXQgY29udGVudHMgPSBhd2FpdCByZWFkUmVtb3RlRmlsZSh7IGhvc3QsIGZpbGVQYXRoOiBSRU1PVEVfQ09ORklHX1BBVEggfSk7XG4gICAgaWYgKCFjb250ZW50cykge1xuICAgICAgICByZXR1cm4geyBob3N0TGFiZWw6IGhvc3QsIHJlcG9Tb3VyY2VzOiBbXSBhcyBzdHJpbmdbXSB9O1xuICAgIH1cbiAgICBsZXQgcGFyc2VkID0gSlNPTi5wYXJzZShjb250ZW50cykgYXMgeyBob3N0TGFiZWw/OiBzdHJpbmc7IHJlcG9Tb3VyY2VzPzogc3RyaW5nW10gfTtcbiAgICByZXR1cm4geyBob3N0TGFiZWw6IHBhcnNlZC5ob3N0TGFiZWwgfHwgaG9zdCwgcmVwb1NvdXJjZXM6IHBhcnNlZC5yZXBvU291cmNlcyB8fCBbXSB9O1xufVxuXG4vKiogUmVhZHMgdGhlIGtleXMgYSBzb3VyY2UncyBjaGVja291dCBjdXJyZW50bHkgaG9sZHMgb24gdGhlIGhvc3QsIHNvIHRoZSBtZXJnZWQgcmVzdWx0IGNhbiBiZVxuICAgIHdvcmtlZCBvdXQgd2l0aG91dCBuZWVkaW5nIHRoYXQgc291cmNlJ3MgcHJpdmF0ZSBrZXkgbG9jYWxseS4gKi9cbmFzeW5jIGZ1bmN0aW9uIHJlYWRSZW1vdGVTb3VyY2VLZXlzKGNvbmZpZzogeyBob3N0OiBzdHJpbmc7IHJlcG9VUkw6IHN0cmluZyB9KSB7XG4gICAgbGV0IHsgaG9zdCwgcmVwb1VSTCB9ID0gY29uZmlnO1xuICAgIGxldCByZXBvUGF0aCA9IHNvdXJjZVJlcG9QYXRoKHJlcG9VUkwpO1xuICAgIGxldCBvdXRwdXQgPSBhd2FpdCBydW5PdmVyU1NIKHtcbiAgICAgICAgaG9zdCxcbiAgICAgICAgc2NyaXB0OiBgJHtTVURPX1BSRUFNQkxFfVxuaWYgJFNVRE8gdGVzdCAtZiBcIiR7cmVwb1BhdGh9L2F1dGhvcml6ZWRfa2V5c1wiOyB0aGVuXG4gICAgJFNVRE8gY2F0IFwiJHtyZXBvUGF0aH0vYXV0aG9yaXplZF9rZXlzXCJcbmVsaWYgJFNVRE8gdGVzdCAtZCBcIiR7cmVwb1BhdGh9XCI7IHRoZW5cbiAgICAkU1VETyBjYXQgXCIke3JlcG9QYXRofVwiLyoucHViIDI+L2Rldi9udWxsIHx8IHRydWVcbmZpYCxcbiAgICAgICAgYWxsb3dGYWlsdXJlOiB0cnVlLFxuICAgIH0pO1xuICAgIHJldHVybiBub3JtYWxpemVLZXlzKG91dHB1dC5zdGRvdXQpO1xufVxuXG5hc3luYyBmdW5jdGlvbiBpbnN0YWxsRGFlbW9uKGNvbmZpZzogeyBob3N0OiBzdHJpbmc7IGhvc3RMYWJlbDogc3RyaW5nOyByZXBvU291cmNlczogc3RyaW5nW10gfSkge1xuICAgIGxldCB7IGhvc3QsIGhvc3RMYWJlbCwgcmVwb1NvdXJjZXMgfSA9IGNvbmZpZztcbiAgICBmb3IgKGxldCBjb21tYW5kIG9mIFtcIm5vZGVcIiwgXCJnaXRcIiwgXCJ5YXJuXCJdKSB7XG4gICAgICAgIGlmICghYXdhaXQgcmVtb3RlQ29tbWFuZEV4aXN0cyh7IGhvc3QsIGNvbW1hbmQgfSkpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgJHtjb21tYW5kfSB0byBiZSBpbnN0YWxsZWQgb24gJHtob3N0fSwgaXQgaXMgbm90LiBJbnN0YWxsIGl0IGFuZCByZXJ1bi5gKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8vIFRoZSBjaGVja291dCBpcyBicm91Z2h0IHRvIHRoZSBsYXRlc3QgY29tbWl0IHJhdGhlciB0aGFuIGEgY29weSBiZWluZyBwdXNoZWQsIHNvIHdoYXQgcnVucyBvblxuICAgIC8vIHRoZSBob3N0IGlzIGV4YWN0bHkgd2hhdCBpcyBvbiBnaXRodWIuXG4gICAgY29uc29sZS5sb2coYFVwZGF0aW5nICR7UkVNT1RFX0NIRUNLT1VUX1BBVEh9IG9uICR7aG9zdH1gKTtcbiAgICBhd2FpdCBydW5PdmVyU1NIKHtcbiAgICAgICAgaG9zdCxcbiAgICAgICAgc2NyaXB0OiBgJHtTVURPX1BSRUFNQkxFfVxuc2V0IC1lXG4kU1VETyBta2RpciAtcCBcIiR7cGF0aC5wb3NpeC5kaXJuYW1lKFJFTU9URV9DSEVDS09VVF9QQVRIKX1cIlxuaWYgJFNVRE8gdGVzdCAtZCBcIiR7UkVNT1RFX0NIRUNLT1VUX1BBVEh9Ly5naXRcIjsgdGhlblxuICAgICRTVURPIGdpdCAtQyBcIiR7UkVNT1RFX0NIRUNLT1VUX1BBVEh9XCIgZmV0Y2ggLS1wcnVuZSBvcmlnaW5cbiAgICAkU1VETyBnaXQgLUMgXCIke1JFTU9URV9DSEVDS09VVF9QQVRIfVwiIHJlc2V0IC0taGFyZCBvcmlnaW4vSEVBRFxuZWxzZVxuICAgICRTVURPIHJtIC1yZiBcIiR7UkVNT1RFX0NIRUNLT1VUX1BBVEh9XCJcbiAgICAkU1VETyBnaXQgY2xvbmUgXCIke1NMSUZUVVRJTFNfVVJMfVwiIFwiJHtSRU1PVEVfQ0hFQ0tPVVRfUEFUSH1cIlxuZmlcbiRTVURPIHlhcm4gLS1jd2QgXCIke1JFTU9URV9DSEVDS09VVF9QQVRIfVwiIGluc3RhbGwgLS1wcm9kdWN0aW9uIC0tbm9uLWludGVyYWN0aXZlXG4jIFRoZSBzaW5nbGUgZmlsZSBkYWVtb24gdGhpcyByZXBsYWNlZCwgbGVmdCBvdmVyIG9uIGEgaG9zdCBzZXQgdXAgYnkgYW4gb2xkZXIgdmVyc2lvbi5cbiRTVURPIHJtIC1mIC9vcHQvcG9ydHNlY3VyZS9wb3J0c2VjdXJlLWRhZW1vbi5qc2AsXG4gICAgfSk7XG4gICAgYXdhaXQgd3JpdGVSZW1vdGVGaWxlKHtcbiAgICAgICAgaG9zdCxcbiAgICAgICAgZmlsZVBhdGg6IFJFTU9URV9DT05GSUdfUEFUSCxcbiAgICAgICAgLy8gT25seSB3aGF0IGRpZmZlcnMgYmV0d2VlbiBtYWNoaW5lcy4gRXZlcnkgcGF0aCB0aGUgZGFlbW9uIHVzZXMgaXMgZGVyaXZlZCBpbiB0aGUgZGFlbW9uXG4gICAgICAgIC8vIGl0c2VsZiwgc28gdGhlcmUgaXMgbm90aGluZyBoZXJlIHRvIGRyaWZ0IG91dCBvZiBzeW5jLlxuICAgICAgICBjb250ZW50czogSlNPTi5zdHJpbmdpZnkoeyBob3N0TGFiZWwsIHJlcG9Tb3VyY2VzIH0sIHVuZGVmaW5lZCwgNCkgKyBcIlxcblwiLFxuICAgICAgICBmaWxlTW9kZTogXCI2MDBcIixcbiAgICAgICAgZGlyZWN0b3J5TW9kZTogXCI3MDBcIixcbiAgICB9KTtcbiAgICBhd2FpdCB3cml0ZVJlbW90ZUZpbGUoe1xuICAgICAgICBob3N0LFxuICAgICAgICBmaWxlUGF0aDogUkVNT1RFX1NFUlZJQ0VfUEFUSCxcbiAgICAgICAgY29udGVudHM6IGF3YWl0IGZzLnJlYWRGaWxlKFNFUlZJQ0VfU09VUkNFLCBcInV0ZjhcIiksXG4gICAgICAgIGZpbGVNb2RlOiBcIjY0NFwiLFxuICAgICAgICBkaXJlY3RvcnlNb2RlOiBcIjc1NVwiLFxuICAgIH0pO1xuICAgIGF3YWl0IHJ1bk92ZXJTU0goe1xuICAgICAgICBob3N0LFxuICAgICAgICBzY3JpcHQ6IGAke1NVRE9fUFJFQU1CTEV9XG5zZXQgLWVcbiRTVURPIHN5c3RlbWN0bCBkYWVtb24tcmVsb2FkXG4kU1VETyBzeXN0ZW1jdGwgZW5hYmxlICR7U0VSVklDRV9OQU1FfVxuJFNVRE8gc3lzdGVtY3RsIHJlc3RhcnQgJHtTRVJWSUNFX05BTUV9YCxcbiAgICB9KTtcblxuICAgIGxldCBzdGF0dXMgPSAoYXdhaXQgcnVuT3ZlclNTSCh7XG4gICAgICAgIGhvc3QsXG4gICAgICAgIHNjcmlwdDogYHN5c3RlbWN0bCBpcy1hY3RpdmUgJHtTRVJWSUNFX05BTUV9IHx8IHRydWVgLFxuICAgICAgICBhbGxvd0ZhaWx1cmU6IHRydWUsXG4gICAgfSkpLnN0ZG91dC50cmltKCk7XG4gICAgaWYgKHN0YXR1cyAhPT0gXCJhY3RpdmVcIikge1xuICAgICAgICBsZXQgam91cm5hbCA9IChhd2FpdCBydW5PdmVyU1NIKHtcbiAgICAgICAgICAgIGhvc3QsXG4gICAgICAgICAgICBzY3JpcHQ6IGAke1NVRE9fUFJFQU1CTEV9XG4kU1VETyBqb3VybmFsY3RsIC11ICR7U0VSVklDRV9OQU1FfSAtbiA0MCAtLW5vLXBhZ2VyIHx8IHRydWVgLFxuICAgICAgICAgICAgYWxsb3dGYWlsdXJlOiB0cnVlLFxuICAgICAgICB9KSkuc3Rkb3V0O1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkICR7U0VSVklDRV9OQU1FfSB0byBiZSBhY3RpdmUgb24gJHtob3N0fSwgd2FzICR7c3RhdHVzfS5cXG4ke2pvdXJuYWwuc2xpY2UoLTIwMDApfWApO1xuICAgIH1cblxuICAgIGxldCBzdGlsbFJlYWNoYWJsZSA9IGF3YWl0IHJ1bk92ZXJTU0goeyBob3N0LCBzY3JpcHQ6IFwiZWNobyByZWFjaGFibGVcIiwgYWxsb3dGYWlsdXJlOiB0cnVlIH0pO1xuICAgIGlmIChzdGlsbFJlYWNoYWJsZS5zdGRvdXQudHJpbSgpICE9PSBcInJlYWNoYWJsZVwiKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIGBFeHBlY3RlZCAke2hvc3R9IHRvIHN0aWxsIGJlIHJlYWNoYWJsZSBhZnRlciB0aGUgZGFlbW9uIHN0YXJ0ZWQsIGl0IGlzIG5vdC5gXG4gICAgICAgICAgICArIGAgQ2hlY2sgY29uc29sZSBhY2Nlc3MgaW1tZWRpYXRlbHkuYFxuICAgICAgICApO1xuICAgIH1cbn1cblxuYXN5bmMgZnVuY3Rpb24gcmVxdWlyZVJlbW90ZVdlYmhvb2soaG9zdDogc3RyaW5nKSB7XG4gICAgbGV0IGNvbnRlbnRzID0gYXdhaXQgcmVhZFJlbW90ZUZpbGUoeyBob3N0LCBmaWxlUGF0aDogREVGQVVMVF9XRUJIT09LX0ZJTEVfUEFUSCB9KTtcbiAgICBpZiAoIWNvbnRlbnRzKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIGBFeHBlY3RlZCBhIERpc2NvcmQgd2ViaG9vayBhdCAke0RFRkFVTFRfV0VCSE9PS19GSUxFX1BBVEh9IG9uICR7aG9zdH0sIG5vIHN1Y2ggZmlsZSBleGlzdHMuYFxuICAgICAgICAgICAgKyBgIFRoZSBkYWVtb24gd2lsbCBub3Qgc3RhcnQgd2l0aG91dCBvbmUuXFxuYFxuICAgICAgICAgICAgKyBgU2V0IGl0IHVwIGZpcnN0OlxcbiAgeWFybiBzZXR1cG5vdGlmeSAke2hvc3R9IDxkaXNjb3JkLXdlYmhvb2stdXJsPmBcbiAgICAgICAgKTtcbiAgICB9XG4gICAgcmV0dXJuIHBhcnNlV2ViaG9va0ZpbGUoeyBjb250ZW50cywgc291cmNlTmFtZTogYCR7aG9zdH06JHtERUZBVUxUX1dFQkhPT0tfRklMRV9QQVRIfWAgfSk7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGFkZFNvdXJjZShjb25maWc6IHsgaG9zdDogc3RyaW5nOyBrZXlQYXRoOiBzdHJpbmc7IHJlcG9VUkw6IHN0cmluZyB9KSB7XG4gICAgbGV0IHsgaG9zdCwga2V5UGF0aCwgcmVwb1VSTCB9ID0gY29uZmlnO1xuICAgIGlmICghYXdhaXQgcGF0aEV4aXN0cyhrZXlQYXRoKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGEgcHJpdmF0ZSBrZXkgYXQgJHtrZXlQYXRofSwgbm8gc3VjaCBmaWxlIGV4aXN0c2ApO1xuICAgIH1cblxuICAgIGNvbnNvbGUubG9nKGBDaGVja2luZyAke3JlcG9VUkx9IGlzIHJlYWNoYWJsZSB3aXRoICR7a2V5UGF0aH1gKTtcbiAgICBsZXQgcmVhY2hhYmxlID0gYXdhaXQgZ2l0V2l0aEtleSh7IGtleVBhdGgsIGFyZ3M6IFtcImxzLXJlbW90ZVwiLCByZXBvVVJMXSwgYWxsb3dGYWlsdXJlOiB0cnVlIH0pO1xuICAgIGlmIChyZWFjaGFibGUuc3RhdHVzICE9PSAwKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIGBFeHBlY3RlZCAke3JlcG9VUkx9IHRvIGJlIHJlYWNoYWJsZSB3aXRoICR7a2V5UGF0aH0sIGdpdCBscy1yZW1vdGUgZmFpbGVkLmBcbiAgICAgICAgICAgICsgYCBUaGUgZGFlbW9uIHdvdWxkIGhhdmUgbm8gd2F5IHRvIGZldGNoIGtleXMuXFxuYFxuICAgICAgICAgICAgKyBgJHsocmVhY2hhYmxlLnN0ZG91dCArIHJlYWNoYWJsZS5zdGRlcnIpLnRyaW0oKS5zbGljZSgwLCBNQVhfRVJST1JfQk9EWV9MRU5HVEgpfWBcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBsZXQgcmVtb3RlQ29uZmlnID0gYXdhaXQgcmVhZFJlbW90ZUNvbmZpZyhob3N0KTtcbiAgICBpZiAocmVtb3RlQ29uZmlnLnJlcG9Tb3VyY2VzLmluY2x1ZGVzKHJlcG9VUkwpKSB7XG4gICAgICAgIGNvbnNvbGUubG9nKGAke2hvc3R9IGFscmVhZHkgaGFzICR7cmVwb1VSTH0sIHJlZnJlc2hpbmcgaXRzIGtleSBhbmQgdGhlIGRhZW1vbi5gKTtcbiAgICB9XG5cbiAgICAvLyBUaGUgbWVyZ2VkIHJlc3VsdCBpcyB3aGF0IHJvb3QgZW5kcyB1cCB3aXRoLCBzbyBvdXIgb3duIGtleSBoYXMgdG8gYmUgc29tZXdoZXJlIGluIGl0LlxuICAgIGNvbnNvbGUubG9nKGBDaGVja2luZyBvdXIgYWNjZXNzIHRvICR7aG9zdH0gc3Vydml2ZXMgdGhlIG1lcmdlZCBrZXlzYCk7XG4gICAgbGV0IG91ckZpbmdlcnByaW50ID0gYXdhaXQgZmluZEF1dGhlbnRpY2F0aW5nRmluZ2VycHJpbnQoaG9zdCk7XG4gICAgbGV0IGluc3BlY3Rpb25QYXRoID0gYXdhaXQgY2xvbmVSZXBvRm9ySW5zcGVjdGlvbih7IHJlcG9VUkwsIGtleVBhdGggfSk7XG4gICAgbGV0IG5ld0tleXMgPSBhd2FpdCByZWFkUmVwb0tleXMoaW5zcGVjdGlvblBhdGgpO1xuICAgIC8vIFdoYXRldmVyIGlzIGFwcGxpZWQgb24gdGhlIGhvc3QgY2FtZSBmcm9tIHRoZSBleGlzdGluZyBzb3VyY2VzLCBzbyBpdCBzdGF5cyBpbiB0aGUgbWVyZ2UuXG4gICAgbGV0IGV4aXN0aW5nS2V5cyA9IG5vcm1hbGl6ZUtleXMoYXdhaXQgcmVhZFJlbW90ZUZpbGUoeyBob3N0LCBmaWxlUGF0aDogUk9PVF9BVVRIT1JJWkVEX0tFWVMgfSkgfHwgXCJcIik7XG4gICAgbGV0IG1lcmdlZEZpbmdlcnByaW50cyA9IGF3YWl0IGZpbmdlcnByaW50S2V5cyhbLi4uZXhpc3RpbmdLZXlzLCAuLi5uZXdLZXlzXSk7XG4gICAgaWYgKCFtZXJnZWRGaW5nZXJwcmludHMuaW5jbHVkZXMob3VyRmluZ2VycHJpbnQpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIGBFeHBlY3RlZCB0aGUga2V5IHdlIHVzZSBmb3IgJHtob3N0fSB0byBiZSBpbiB0aGUgbWVyZ2VkIGtleXMsIGl0IGlzIG5vdC5cXG5gXG4gICAgICAgICAgICArIGBPdXJzOiAgICR7b3VyRmluZ2VycHJpbnR9XFxuYFxuICAgICAgICAgICAgKyBgTWVyZ2VkOiAke21lcmdlZEZpbmdlcnByaW50cy5qb2luKFwiXFxuICAgICAgICBcIikgfHwgXCIobm9uZSlcIn1cXG5gXG4gICAgICAgICAgICArIGBUaGUgZGFlbW9uIHJlcGxhY2VzIHJvb3QncyBhdXRob3JpemVkX2tleXMgd2l0aCB0aGUgbWVyZ2VkIHNvdXJjZXMsIHNvIHRoaXMgd291bGRgXG4gICAgICAgICAgICArIGAgbG9jayB5b3Ugb3V0IG9mICR7aG9zdH0uIEFkZCB5b3VyIHB1YmxpYyBrZXkgdG8gJHtyZXBvVVJMfSBmaXJzdC5gXG4gICAgICAgICk7XG4gICAgfVxuICAgIGNvbnNvbGUubG9nKGBPdXIga2V5ICR7b3VyRmluZ2VycHJpbnR9IGlzIGluIHRoZSBtZXJnZWQga2V5cywgYWNjZXNzIHdpbGwgc3Vydml2ZS5gKTtcblxuICAgIGF3YWl0IGVuc3VyZVJldm9rZVJlcG8oeyBrZXlQYXRoLCByZXBvVVJMIH0pO1xuXG4gICAgLy8gRGVwbG95aW5nIGEgcmVwbyB0aGF0IHN0aWxsIGhvbGRzIGEgcmV2b2tlZCBrZXkgd291bGQgaGFuZCBpdCBiYWNrIHRvIGV2ZXJ5IG1hY2hpbmUuXG4gICAgbGV0IHJldm9rZWQgPSBhd2FpdCByZXZva2VkS2V5c0luUmVwbyh7IHJlcG9QYXRoOiBpbnNwZWN0aW9uUGF0aCwgc291cmNlVVJMOiByZXBvVVJMIH0pO1xuICAgIGlmIChyZXZva2VkLmxlbmd0aCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBgRXhwZWN0ZWQgJHtyZXBvVVJMfSB0byBob2xkIG5vIHJldm9rZWQga2V5cywgaXQgaG9sZHMgJHtyZXZva2VkLmxlbmd0aH06XFxuYFxuICAgICAgICAgICAgKyByZXZva2VkLm1hcChlbnRyeSA9PiBgICAke2VudHJ5LnJldm9jYXRpb24uZmluZ2VycHJpbnR9IHJldm9rZWQgYnlgXG4gICAgICAgICAgICAgICAgKyBgICR7ZW50cnkucmV2b2NhdGlvbi5yZXZva2VkQnkgfHwgXCI/XCJ9IGFmdGVyIHVzZSBmcm9tICR7ZW50cnkucmV2b2NhdGlvbi5hdHRlbXB0Py5pcCB8fCBcIj9cIn1gKS5qb2luKFwiXFxuXCIpXG4gICAgICAgICAgICArIGBcXG5EZWxldGUgdGhlbSBmcm9tIHRoZSByZXBvLCBvciBydW4gXCJ5YXJuIHVucmV2b2tlXCIgdGhlcmUgdG8gYWxsb3cgdGhlbSBhZ2Fpbi5gXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgbGV0IHdlYmhvb2tVUkwgPSBhd2FpdCByZXF1aXJlUmVtb3RlV2ViaG9vayhob3N0KTtcbiAgICBjb25zb2xlLmxvZyhgJHtob3N0fSBub3RpZmllcyAke3dlYmhvb2tVUkx9YCk7XG5cbiAgICBhd2FpdCB3cml0ZVJlbW90ZUZpbGUoe1xuICAgICAgICBob3N0LFxuICAgICAgICBmaWxlUGF0aDogc291cmNlS2V5UGF0aChyZXBvVVJMKSxcbiAgICAgICAgY29udGVudHM6IGF3YWl0IGZzLnJlYWRGaWxlKGtleVBhdGgsIFwidXRmOFwiKSxcbiAgICAgICAgZmlsZU1vZGU6IFwiNjAwXCIsXG4gICAgICAgIGRpcmVjdG9yeU1vZGU6IFwiNzAwXCIsXG4gICAgfSk7XG5cbiAgICBsZXQgcmVwb1NvdXJjZXMgPSByZW1vdGVDb25maWcucmVwb1NvdXJjZXMuZmlsdGVyKHNvdXJjZSA9PiBzb3VyY2UgIT09IHJlcG9VUkwpO1xuICAgIHJlcG9Tb3VyY2VzLnB1c2gocmVwb1VSTCk7XG4gICAgYXdhaXQgaW5zdGFsbERhZW1vbih7IGhvc3QsIGhvc3RMYWJlbDogcmVtb3RlQ29uZmlnLmhvc3RMYWJlbCwgcmVwb1NvdXJjZXMgfSk7XG4gICAgY29uc29sZS5sb2coYCR7cmVwb1VSTH0gYWRkZWQgdG8gJHtob3N0fS4gJHtyZXBvU291cmNlcy5sZW5ndGh9IHNvdXJjZShzKSBub3cgbWVyZ2VkLmApO1xufVxuXG5hc3luYyBmdW5jdGlvbiByZW1vdmVTb3VyY2UoY29uZmlnOiB7IGhvc3Q6IHN0cmluZzsgcmVwb1VSTDogc3RyaW5nIH0pIHtcbiAgICBsZXQgeyBob3N0LCByZXBvVVJMIH0gPSBjb25maWc7XG4gICAgbGV0IHJlbW90ZUNvbmZpZyA9IGF3YWl0IHJlYWRSZW1vdGVDb25maWcoaG9zdCk7XG4gICAgaWYgKCFyZW1vdGVDb25maWcucmVwb1NvdXJjZXMuaW5jbHVkZXMocmVwb1VSTCkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgYEV4cGVjdGVkICR7cmVwb1VSTH0gdG8gYmUgYSBzb3VyY2Ugb24gJHtob3N0fSwgaXQgaXMgbm90LlxcbmBcbiAgICAgICAgICAgICsgYENvbmZpZ3VyZWQ6XFxuICAke3JlbW90ZUNvbmZpZy5yZXBvU291cmNlcy5qb2luKFwiXFxuICBcIikgfHwgXCIobm9uZSlcIn1gXG4gICAgICAgICk7XG4gICAgfVxuICAgIGxldCByZXBvU291cmNlcyA9IHJlbW90ZUNvbmZpZy5yZXBvU291cmNlcy5maWx0ZXIoc291cmNlID0+IHNvdXJjZSAhPT0gcmVwb1VSTCk7XG5cbiAgICBpZiAocmVwb1NvdXJjZXMubGVuZ3RoKSB7XG4gICAgICAgIC8vIFRoZSBrZXlzIGxlZnQgb3ZlciBhcmUgd2hhdCByb290IGdldHMsIHNvIG91ciBvd24ga2V5IGhhcyB0byBiZSBhbW9uZyB0aGVtLlxuICAgICAgICBjb25zb2xlLmxvZyhgQ2hlY2tpbmcgb3VyIGFjY2VzcyB0byAke2hvc3R9IHN1cnZpdmVzIHdpdGhvdXQgJHtyZXBvVVJMfWApO1xuICAgICAgICBsZXQgb3VyRmluZ2VycHJpbnQgPSBhd2FpdCBmaW5kQXV0aGVudGljYXRpbmdGaW5nZXJwcmludChob3N0KTtcbiAgICAgICAgbGV0IHJlbWFpbmluZ0tleXM6IHN0cmluZ1tdID0gW107XG4gICAgICAgIGZvciAobGV0IHNvdXJjZSBvZiByZXBvU291cmNlcykge1xuICAgICAgICAgICAgcmVtYWluaW5nS2V5cy5wdXNoKC4uLmF3YWl0IHJlYWRSZW1vdGVTb3VyY2VLZXlzKHsgaG9zdCwgcmVwb1VSTDogc291cmNlIH0pKTtcbiAgICAgICAgfVxuICAgICAgICBsZXQgcmVtYWluaW5nRmluZ2VycHJpbnRzID0gYXdhaXQgZmluZ2VycHJpbnRLZXlzKHJlbWFpbmluZ0tleXMpO1xuICAgICAgICBpZiAoIXJlbWFpbmluZ0ZpbmdlcnByaW50cy5pbmNsdWRlcyhvdXJGaW5nZXJwcmludCkpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICAgICBgRXhwZWN0ZWQgdGhlIGtleSB3ZSB1c2UgZm9yICR7aG9zdH0gdG8gc3RpbGwgYmUgaW4gdGhlIHJlbWFpbmluZyBzb3VyY2VzLCBpdCBpcyBub3QuXFxuYFxuICAgICAgICAgICAgICAgICsgYE91cnM6ICAgICAgJHtvdXJGaW5nZXJwcmludH1cXG5gXG4gICAgICAgICAgICAgICAgKyBgUmVtYWluaW5nOiAke3JlbWFpbmluZ0ZpbmdlcnByaW50cy5qb2luKFwiXFxuICAgICAgICAgICBcIikgfHwgXCIobm9uZSlcIn1cXG5gXG4gICAgICAgICAgICAgICAgKyBgUmVtb3ZpbmcgJHtyZXBvVVJMfSB3b3VsZCBsb2NrIHlvdSBvdXQgb2YgJHtob3N0fS5gXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgICAgLy8gTm90aGluZyBsZWZ0IHRvIG1lcmdlLCBzbyB0aGUgZGFlbW9uIGxlYXZlcyByb290J3MgYXV0aG9yaXplZF9rZXlzIGV4YWN0bHkgYXMgaXQgaXMuXG4gICAgICAgIGNvbnNvbGUubG9nKGAke3JlcG9VUkx9IGlzIHRoZSBsYXN0IHNvdXJjZSwgc28gcm9vdCdzIGF1dGhvcml6ZWRfa2V5cyBzdGF5cyBhcyBpdCBpcyBub3cuYCk7XG4gICAgfVxuXG4gICAgYXdhaXQgcmVxdWlyZVJlbW90ZVdlYmhvb2soaG9zdCk7XG4gICAgYXdhaXQgcnVuT3ZlclNTSCh7XG4gICAgICAgIGhvc3QsXG4gICAgICAgIHNjcmlwdDogYCR7U1VET19QUkVBTUJMRX1cbiRTVURPIHJtIC1mIFwiJHtzb3VyY2VLZXlQYXRoKHJlcG9VUkwpfVwiXG4kU1VETyBybSAtcmYgXCIke3NvdXJjZVJlcG9QYXRoKHJlcG9VUkwpfVwiYCxcbiAgICB9KTtcbiAgICBhd2FpdCBpbnN0YWxsRGFlbW9uKHsgaG9zdCwgaG9zdExhYmVsOiByZW1vdGVDb25maWcuaG9zdExhYmVsLCByZXBvU291cmNlcyB9KTtcbiAgICBjb25zb2xlLmxvZyhgJHtyZXBvVVJMfSByZW1vdmVkIGZyb20gJHtob3N0fS4gJHtyZXBvU291cmNlcy5sZW5ndGh9IHNvdXJjZShzKSBsZWZ0LmApO1xufVxuXG4vKiogQW5zd2VycyBcIndobyBjYW4gbG9nIGludG8gdGhpcyBib3gsIGFuZCB3aGljaCByZXBvIHNheXMgc29cIi4gVGhlIHBhdGhzIHRoZSBkYWVtb24gdXNlcyBhcmVcbiAgICBsZWZ0IG91dCBvbiBwdXJwb3NlLCB0aGV5IGFyZSBwbHVtYmluZyByYXRoZXIgdGhhbiBzb21ldGhpbmcgdG8gYWN0IG9uLiAqL1xuLyoqIFRoZSBkYWVtb24gaXMgaW5zdGFsbGVkIGZyb20gdGhpcyBjaGVja291dCwgc28gdGhlIGNoZWNrb3V0IGlzIGJyb3VnaHQgdXAgdG8gZGF0ZSBmaXJzdC5cbiAgICBBbnl0aGluZyB0aGF0IHN0b3BzIHRoZSBwdWxsIC0gYSBjb25mbGljdCwgbG9jYWwgY29tbWl0cywgbm8gdXBzdHJlYW0gLSBzdG9wcyB0aGUgdXBkYXRlIHRvbyxcbiAgICByYXRoZXIgdGhhbiBxdWlldGx5IGluc3RhbGxpbmcgd2hhdGV2ZXIgaGFwcGVuZWQgdG8gYmUgb24gZGlzay4gKi9cbmFzeW5jIGZ1bmN0aW9uIHB1bGxMb2NhbENoZWNrb3V0KCkge1xuICAgIGxldCB0b3BMZXZlbCA9IGF3YWl0IHNwYXduUHJvbWlzZSh7IGNvbW1hbmQ6IFwiZ2l0XCIsIGFyZ3M6IFtcInJldi1wYXJzZVwiLCBcIi0tc2hvdy10b3BsZXZlbFwiXSwgY3dkOiBfX2Rpcm5hbWUgfSk7XG4gICAgaWYgKHRvcExldmVsLnN0YXR1cyAhPT0gMCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBgRXhwZWN0ZWQgJHtfX2Rpcm5hbWV9IHRvIGJlIGluc2lkZSBhIGdpdCBjaGVja291dCwgaXQgaXMgbm90LmBcbiAgICAgICAgICAgICsgYCB1cGRhdGUgaW5zdGFsbHMgdGhlIGRhZW1vbiBmcm9tIHRoaXMgY2hlY2tvdXQsIHNvIGl0IGhhcyB0byBiZSBvbmUuYFxuICAgICAgICApO1xuICAgIH1cbiAgICBsZXQgcmVwb1BhdGggPSB0b3BMZXZlbC5zdGRvdXQudHJpbSgpO1xuICAgIGxldCBwdWxsID0gYXdhaXQgc3Bhd25Qcm9taXNlKHsgY29tbWFuZDogXCJnaXRcIiwgYXJnczogW1wicHVsbFwiLCBcIi0tZmYtb25seVwiXSwgY3dkOiByZXBvUGF0aCB9KTtcbiAgICBpZiAocHVsbC5zdGF0dXMgIT09IDApIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgYEV4cGVjdGVkIGdpdCBwdWxsIGluICR7cmVwb1BhdGh9IHRvIHN1Y2NlZWQsIGl0IGRpZCBub3QsIHNvIG5vdGhpbmcgd2FzIGluc3RhbGxlZC5cXG5gXG4gICAgICAgICAgICArIGAkeyhwdWxsLnN0ZG91dCArIHB1bGwuc3RkZXJyKS50cmltKCkuc2xpY2UoMCwgTUFYX0VSUk9SX0JPRFlfTEVOR1RIKX1gXG4gICAgICAgICk7XG4gICAgfVxuICAgIC8vIFRoZSBob3N0IGluc3RhbGxzIGZyb20gZ2l0aHViLCBub3QgZnJvbSBoZXJlLCBzbyBsb2NhbCBjb21taXRzIHRoYXQgaGF2ZSBub3QgYmVlbiBwdXNoZWRcbiAgICAvLyBhcmUgbm90IHdoYXQgaXQgd2lsbCBydW4uXG4gICAgbGV0IGxvY2FsID0gYXdhaXQgc3Bhd25Qcm9taXNlKHsgY29tbWFuZDogXCJnaXRcIiwgYXJnczogW1wicmV2LXBhcnNlXCIsIFwiSEVBRFwiXSwgY3dkOiByZXBvUGF0aCB9KTtcbiAgICBsZXQgcmVtb3RlID0gYXdhaXQgc3Bhd25Qcm9taXNlKHsgY29tbWFuZDogXCJnaXRcIiwgYXJnczogW1wicmV2LXBhcnNlXCIsIFwiQHt1fVwiXSwgY3dkOiByZXBvUGF0aCB9KTtcbiAgICBpZiAobG9jYWwuc3Rkb3V0LnRyaW0oKSAhPT0gcmVtb3RlLnN0ZG91dC50cmltKCkpIHtcbiAgICAgICAgY29uc29sZS5sb2coYFdBUk5JTkc6ICR7cmVwb1BhdGh9IGhhcyBjb21taXRzIHRoYXQgYXJlIG5vdCBwdXNoZWQuIFRoZSBob3N0IGluc3RhbGxzIGZyb20gZ2l0aHViLGApO1xuICAgICAgICBjb25zb2xlLmxvZyhgICAgICAgICAgc28gaXQgd2lsbCBydW4gdGhlIHB1c2hlZCB2ZXJzaW9uLCBub3Qgd2hhdCBpcyBoZXJlLmApO1xuICAgIH1cbiAgICBjb25zb2xlLmxvZyhgUHVsbGVkICR7cmVwb1BhdGh9YCk7XG59XG5cbi8qKiBQdXNoZXMgdGhlIGN1cnJlbnQgZGFlbW9uIG9udG8gYSBob3N0IHRoYXQgYWxyZWFkeSBoYXMgb25lLCBmb3Igd2hlbiB0aGlzIGNvZGUgaGFzIG1vdmVkIG9uLlxuICAgIE5vdGhpbmcgYWJvdXQgd2hpY2gga2V5cyB0aGUgaG9zdCB0cnVzdHMgaXMgdG91Y2hlZC4gKi9cbmFzeW5jIGZ1bmN0aW9uIHVwZGF0ZURhZW1vbihob3N0OiBzdHJpbmcpIHtcbiAgICAvLyBQdWxsZWQgYmVmb3JlIGFueXRoaW5nIGVsc2UsIHNvIGEgY2hlY2tvdXQgdGhhdCBjYW5ub3QgYmUgYnJvdWdodCB1cCB0byBkYXRlIGZhaWxzIGhlcmVcbiAgICAvLyByYXRoZXIgdGhhbiBhZnRlciB3ZSBoYXZlIGFscmVhZHkgc3RhcnRlZCBjaGFuZ2luZyB0aGUgaG9zdC5cbiAgICBhd2FpdCBwdWxsTG9jYWxDaGVja291dCgpO1xuICAgIGxldCBjb250ZW50cyA9IGF3YWl0IHJlYWRSZW1vdGVGaWxlKHsgaG9zdCwgZmlsZVBhdGg6IFJFTU9URV9DT05GSUdfUEFUSCB9KTtcbiAgICBpZiAoIWNvbnRlbnRzKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIGBFeHBlY3RlZCAke2hvc3R9IHRvIGFscmVhZHkgaGF2ZSBwb3J0c2VjdXJlLCAke1JFTU9URV9DT05GSUdfUEFUSH0gZG9lcyBub3QgZXhpc3QuXFxuYFxuICAgICAgICAgICAgKyBgU2V0IGl0IHVwIHdpdGg6XFxuICB5YXJuIHNlY3VyZXNzaCAke2hvc3R9IGFkZCA8cmVwby1wcml2YXRlLWtleT4gW3JlcG8tdXJsXWBcbiAgICAgICAgKTtcbiAgICB9XG4gICAgbGV0IHBhcnNlZCA9IEpTT04ucGFyc2UoY29udGVudHMpIGFzIHsgaG9zdExhYmVsPzogc3RyaW5nOyByZXBvU291cmNlcz86IHN0cmluZ1tdIH07XG4gICAgbGV0IHJlcG9Tb3VyY2VzID0gcGFyc2VkLnJlcG9Tb3VyY2VzIHx8IFtdO1xuICAgIGF3YWl0IHJlcXVpcmVSZW1vdGVXZWJob29rKGhvc3QpO1xuICAgIGF3YWl0IGVuc3VyZVJldm9rZVJlcG9zRXhpc3QocmVwb1NvdXJjZXMpO1xuICAgIGF3YWl0IGluc3RhbGxEYWVtb24oeyBob3N0LCBob3N0TGFiZWw6IHBhcnNlZC5ob3N0TGFiZWwgfHwgaG9zdCwgcmVwb1NvdXJjZXMgfSk7XG4gICAgY29uc29sZS5sb2coYFVwZGF0ZWQgdGhlIGRhZW1vbiBvbiAke2hvc3R9LiAke3JlcG9Tb3VyY2VzLmxlbmd0aH0gc291cmNlKHMpLCB1bmNoYW5nZWQuYCk7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGxpc3RTb3VyY2VzKGhvc3Q6IHN0cmluZykge1xuICAgIGxldCByZW1vdGVDb25maWcgPSBhd2FpdCByZWFkUmVtb3RlQ29uZmlnKGhvc3QpO1xuICAgIGlmICghcmVtb3RlQ29uZmlnLnJlcG9Tb3VyY2VzLmxlbmd0aCkge1xuICAgICAgICBjb25zb2xlLmxvZyhgJHtob3N0fSBoYXMgbm8ga2V5IHNvdXJjZXMuIHJvb3QncyBhdXRob3JpemVkX2tleXMgaXMgbGVmdCBleGFjdGx5IGFzIGl0IGlzLmApO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnNvbGUubG9nKGAke2hvc3R9IGxldHMgcm9vdCBsb2cgaW4gd2l0aCB0aGUga2V5cyBmcm9tICR7cmVtb3RlQ29uZmlnLnJlcG9Tb3VyY2VzLmxlbmd0aH0gcmVwbyhzKTpgKTtcbiAgICBsZXQgbWVyZ2VkID0gbmV3IFNldDxzdHJpbmc+KCk7XG4gICAgZm9yIChsZXQgcmVwb1VSTCBvZiByZW1vdGVDb25maWcucmVwb1NvdXJjZXMpIHtcbiAgICAgICAgbGV0IGtleXMgPSBhd2FpdCByZWFkUmVtb3RlU291cmNlS2V5cyh7IGhvc3QsIHJlcG9VUkwgfSk7XG4gICAgICAgIGNvbnNvbGUubG9nKGBcXG4gICR7cmVwb1VSTH1gKTtcbiAgICAgICAgaWYgKCFrZXlzLmxlbmd0aCkge1xuICAgICAgICAgICAgY29uc29sZS5sb2coYCAgICBncmFudHMgbm8ga2V5cyAtIHRoZSBjaGVja291dCBpcyBtaXNzaW5nIG9yIGVtcHR5YCk7XG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgICBjb25zb2xlLmxvZyhgICAgIGdyYW50cyAke2tleXMubGVuZ3RofSBrZXkocyk6YCk7XG4gICAgICAgIGZvciAobGV0IGtleSBvZiBrZXlzKSB7XG4gICAgICAgICAgICBjb25zb2xlLmxvZyhgICAgICAgJHtzdW1tYXJpemVLZXkoa2V5KX1gKTtcbiAgICAgICAgICAgIG1lcmdlZC5hZGQoa2V5KTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBpZiAocmVtb3RlQ29uZmlnLnJlcG9Tb3VyY2VzLmxlbmd0aCA+IDEpIHtcbiAgICAgICAgY29uc29sZS5sb2coYFxcbiR7bWVyZ2VkLnNpemV9IGtleShzKSBpbiB0b3RhbCBvbmNlIGR1cGxpY2F0ZXMgYXJlIG1lcmdlZC5gKTtcbiAgICB9XG59XG5cbi8qKiBUaGUgdmVyYiBpcyBhIGZpeGVkIHdvcmQgcmF0aGVyIHRoYW4gYSBwb3NpdGlvbiwgc28gaXQgaXMgcHVsbGVkIG91dCBvZiB0aGUgYXJndW1lbnRzIHdoZXJldmVyXG4gICAgaXQgd2FzIHR5cGVkIGFuZCBldmVyeXRoaW5nIGxlZnQgb3ZlciBpcyBwb3NpdGlvbmFsLiAqL1xuZnVuY3Rpb24gcGFyc2VBcmdzKGFyZ3Y6IHN0cmluZ1tdKSB7XG4gICAgbGV0IHZlcmJzID0gYXJndi5maWx0ZXIoYXJnID0+IFZFUkJTLmluY2x1ZGVzKGFyZykpO1xuICAgIGlmICghdmVyYnMubGVuZ3RoKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgb25lIG9mICR7VkVSQlMuam9pbihcIiwgXCIpfSBzb21ld2hlcmUgaW4gdGhlIGFyZ3VtZW50cywgd2FzICR7YXJndi5qb2luKFwiIFwiKSB8fCBcIihub3RoaW5nKVwifVxcbiR7VVNBR0V9YCk7XG4gICAgfVxuICAgIGlmICh2ZXJicy5sZW5ndGggPiAxKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgb25lIG9mICR7VkVSQlMuam9pbihcIiwgXCIpfSwgd2FzICR7dmVyYnMuam9pbihcIiBhbmQgXCIpfVxcbiR7VVNBR0V9YCk7XG4gICAgfVxuICAgIGxldCB2ZXJiID0gdmVyYnNbMF07XG4gICAgbGV0IFtob3N0LCAuLi5yZXN0XSA9IGFyZ3YuZmlsdGVyKGFyZyA9PiBhcmcgIT09IHZlcmIpO1xuICAgIGlmICghaG9zdCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGEgaG9zdCwgd2FzIG5vdGhpbmdcXG4ke1VTQUdFfWApO1xuICAgIH1cbiAgICByZXR1cm4geyB2ZXJiLCBob3N0LCByZXN0IH07XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBtYWluKCkge1xuICAgIGxldCB7IHZlcmIsIGhvc3QsIHJlc3QgfSA9IHBhcnNlQXJncyhwcm9jZXNzLmFyZ3Yuc2xpY2UoMikpO1xuXG4gICAgaWYgKHZlcmIgPT09IFwibGlzdFwiKSB7XG4gICAgICAgIGF3YWl0IGxpc3RTb3VyY2VzKGhvc3QpO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICh2ZXJiID09PSBcInVwZGF0ZVwiKSB7XG4gICAgICAgIGlmIChyZXN0Lmxlbmd0aCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBub3RoaW5nIGFmdGVyIHVwZGF0ZSwgd2FzICR7cmVzdC5sZW5ndGh9IGFyZ3VtZW50KHMpXFxuJHtVU0FHRX1gKTtcbiAgICAgICAgfVxuICAgICAgICBhd2FpdCB1cGRhdGVEYWVtb24oaG9zdCk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKHZlcmIgPT09IFwiYWRkXCIpIHtcbiAgICAgICAgaWYgKCFyZXN0Lmxlbmd0aCB8fCByZXN0Lmxlbmd0aCA+IDIpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgYSBwcml2YXRlIGtleSBhbmQgb3B0aW9uYWxseSBhIHJlcG8gdXJsLCB3YXMgJHtyZXN0Lmxlbmd0aH0gYXJndW1lbnQocylcXG4ke1VTQUdFfWApO1xuICAgICAgICB9XG4gICAgICAgIGF3YWl0IGFkZFNvdXJjZSh7IGhvc3QsIGtleVBhdGg6IGV4cGFuZEhvbWUocmVzdFswXSksIHJlcG9VUkw6IGF3YWl0IHJlc29sdmVSZXBvVVJMKHJlc3RbMV0pIH0pO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmIChyZXN0Lmxlbmd0aCA+IDEpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBhdCBtb3N0IGEgcmVwbyB1cmwgdG8gcmVtb3ZlLCB3YXMgJHtyZXN0Lmxlbmd0aH0gYXJndW1lbnQocylcXG4ke1VTQUdFfWApO1xuICAgIH1cbiAgICBhd2FpdCByZW1vdmVTb3VyY2UoeyBob3N0LCByZXBvVVJMOiBhd2FpdCByZXNvbHZlUmVwb1VSTChyZXN0WzBdKSB9KTtcbn1cbiJdfQ==
|
|
552
|
+
/* _JS_SOURCE_HASH = "da009459c73fcae093447abdc3e6c2911f8b97eac1ceafe51e63b71a7ad5e887"; */
|