@takodotid/circuit 0.43.1 → 1.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -37
- package/package.json +21 -58
- package/src/checks/across.ts +402 -0
- package/src/checks/built-in.ts +256 -0
- package/src/checks/device.ts +436 -0
- package/src/checks/index.ts +53 -0
- package/src/cli/advisories.ts +108 -0
- package/src/cli/app.ts +71 -0
- package/src/cli/editor-types.generated.ts +30 -0
- package/src/cli/editor-types.ts +69 -0
- package/src/cli/format.ts +124 -0
- package/src/cli/git-hooks.ts +79 -0
- package/src/cli/index.ts +350 -1170
- package/src/cli/migrate.ts +554 -0
- package/src/cli/project-commands.ts +656 -0
- package/src/cli/releases.ts +374 -0
- package/src/cli/remember.ts +339 -0
- package/src/cli/rename.ts +27 -33
- package/src/cli/secrets-password.ts +60 -0
- package/src/cli/secrets.ts +258 -0
- package/src/cli/setup.ts +300 -0
- package/src/cli/signing.ts +23 -0
- package/src/cli/text-editor.ts +268 -0
- package/src/cli/ui.ts +298 -0
- package/src/cli/version.ts +2 -0
- package/src/cli/versions.ts +158 -0
- package/src/config/address.ts +102 -0
- package/src/config/circuit.d.ts +30 -0
- package/src/config/common.ts +65 -0
- package/src/config/communities.ts +479 -0
- package/src/config/define.ts +684 -0
- package/src/config/device.ts +164 -0
- package/src/config/firewall.ts +150 -0
- package/src/config/index.ts +22 -0
- package/src/{schema → config}/interfaces.ts +121 -85
- package/src/config/keys.ts +434 -0
- package/src/config/management.ts +61 -0
- package/src/config/merge.ts +335 -0
- package/src/config/models.ts +70 -0
- package/src/config/monitoring.ts +64 -0
- package/src/config/network.ts +180 -0
- package/src/config/presets.ts +149 -0
- package/src/config/routing.ts +401 -0
- package/src/config/services.ts +51 -0
- package/src/config/switching.ts +84 -0
- package/src/config/system.ts +103 -0
- package/src/{schema → config}/time-zones.ts +1 -1
- package/src/core/address.ts +1 -1
- package/src/core/cabling.ts +8 -43
- package/src/core/graph.ts +40 -0
- package/src/{integrations → core}/registries.ts +5 -3
- package/src/core/secrets-file.ts +539 -0
- package/src/core/secrets.ts +83 -64
- package/src/core/topology.ts +103 -146
- package/src/core/utils.ts +12 -1
- package/src/model.ts +83 -0
- package/src/platforms/derive.ts +64 -37
- package/src/platforms/finding.ts +9 -0
- package/src/platforms/fortios/firewall.ts +76 -64
- package/src/platforms/fortios/index.ts +189 -125
- package/src/platforms/fortios/names.ts +51 -18
- package/src/platforms/fortios/render.ts +69 -62
- package/src/platforms/fortios/routing.ts +89 -58
- package/src/platforms/fortios/vdoms.ts +47 -16
- package/src/platforms/junos/TRAPS.md +2 -2
- package/src/platforms/junos/bgp.ts +63 -42
- package/src/platforms/junos/index.ts +122 -89
- package/src/platforms/junos/render.ts +139 -104
- package/src/platforms/platform.ts +10 -8
- package/src/platforms/raisecom-ros/TRAPS.md +1 -1
- package/src/platforms/raisecom-ros/index.ts +89 -70
- package/src/platforms/raisecom-ros/render.ts +49 -45
- package/src/platforms/routeros/TRAPS.md +3 -3
- package/src/platforms/routeros/index.ts +121 -60
- package/src/platforms/routeros/render.ts +214 -195
- package/src/platforms/vrp/TRAPS.md +1 -1
- package/src/platforms/vrp/index.ts +74 -50
- package/src/platforms/vrp/render.ts +67 -56
- package/src/project/project.ts +725 -0
- package/src/studio/app/env.d.ts +9 -0
- package/src/studio/app/index.html +18 -0
- package/src/studio/app/src/App.vue +95 -0
- package/src/studio/app/src/components/AddressesView.vue +38 -0
- package/src/studio/app/src/components/CablesView.vue +29 -0
- package/src/studio/app/src/components/CompareView.vue +160 -0
- package/src/studio/app/src/components/DataTable.vue +49 -0
- package/src/studio/app/src/components/DeviceView.vue +237 -0
- package/src/studio/app/src/components/DevicesView.vue +32 -0
- package/src/studio/app/src/components/Faceplate.vue +75 -0
- package/src/studio/app/src/components/GraphView.vue +282 -0
- package/src/studio/app/src/components/LineDiff.vue +24 -0
- package/src/studio/app/src/components/ProblemsView.vue +42 -0
- package/src/studio/app/src/components/SearchView.vue +72 -0
- package/src/studio/app/src/components/SecretsView.vue +112 -0
- package/src/studio/app/src/components/SessionsView.vue +35 -0
- package/src/studio/app/src/components/Settings.vue +95 -0
- package/src/studio/app/src/components/ToolsView.vue +118 -0
- package/src/studio/app/src/components/VlansView.vue +50 -0
- package/src/studio/app/src/lib/csv.ts +19 -0
- package/src/studio/{public/diff.js → app/src/lib/diff.ts} +40 -35
- package/src/studio/app/src/lib/format.ts +38 -0
- package/src/studio/app/src/lib/net.ts +68 -0
- package/src/studio/app/src/lib/secrets-crypto.ts +35 -0
- package/src/studio/app/src/lib/store.ts +71 -0
- package/src/studio/app/src/lib/tools.ts +217 -0
- package/src/studio/app/src/main.ts +9 -0
- package/src/studio/{public → app/src}/style.css +14 -4
- package/src/studio/app/tsconfig.json +17 -0
- package/src/studio/app/vite.config.ts +16 -0
- package/src/studio/assets.generated.ts +3 -0
- package/src/studio/data.ts +96 -188
- package/src/studio/git.ts +27 -43
- package/src/studio/server.ts +196 -107
- package/src/studio/types.ts +141 -0
- package/src/transport/console.ts +3 -3
- package/src/transport/ssh.ts +1 -1
- package/dist/chunks/index-3b343xva.js +0 -177
- package/dist/chunks/index-6wb41q67.js +0 -62
- package/dist/chunks/index-n7w3tg5y.js +0 -149
- package/dist/chunks/index-yzw86rc8.js +0 -921
- package/dist/chunks/new-w3zys5y4.js +0 -172
- package/dist/chunks/server-cxr9ynxc.js +0 -259
- package/dist/cli/index.js +0 -12922
- package/dist/helpers/index.js +0 -881
- package/dist/index.js +0 -28
- package/dist/types/core/address-space.d.ts +0 -62
- package/dist/types/core/address.d.ts +0 -44
- package/dist/types/core/cabling.d.ts +0 -41
- package/dist/types/core/cross-checks.d.ts +0 -28
- package/dist/types/core/define.d.ts +0 -79
- package/dist/types/core/project-files.d.ts +0 -12
- package/dist/types/core/schema-docs.d.ts +0 -25
- package/dist/types/core/secrets.d.ts +0 -62
- package/dist/types/core/topology.d.ts +0 -96
- package/dist/types/core/utils.d.ts +0 -10
- package/dist/types/core/validator.d.ts +0 -23
- package/dist/types/helpers/bgp.d.ts +0 -195
- package/dist/types/helpers/cables.d.ts +0 -69
- package/dist/types/helpers/checks.d.ts +0 -36
- package/dist/types/helpers/exchange.d.ts +0 -55
- package/dist/types/helpers/filters.d.ts +0 -51
- package/dist/types/helpers/index.d.ts +0 -14
- package/dist/types/helpers/route-server.d.ts +0 -52
- package/dist/types/helpers/upstreams.d.ts +0 -192
- package/dist/types/index.d.ts +0 -10
- package/dist/types/integrations/librenms.d.ts +0 -60
- package/dist/types/integrations/netbox.d.ts +0 -89
- package/dist/types/integrations/peeringdb.d.ts +0 -24
- package/dist/types/integrations/prometheus.d.ts +0 -24
- package/dist/types/integrations/registries.d.ts +0 -8
- package/dist/types/platforms/bgp-wait.d.ts +0 -24
- package/dist/types/platforms/catalog.d.ts +0 -109
- package/dist/types/platforms/derive.d.ts +0 -17
- package/dist/types/platforms/fortios/firewall.d.ts +0 -47
- package/dist/types/platforms/fortios/index.d.ts +0 -140
- package/dist/types/platforms/fortios/models.d.ts +0 -14
- package/dist/types/platforms/fortios/names.d.ts +0 -52
- package/dist/types/platforms/fortios/plan.d.ts +0 -114
- package/dist/types/platforms/fortios/render.d.ts +0 -77
- package/dist/types/platforms/fortios/routing.d.ts +0 -56
- package/dist/types/platforms/fortios/tree.d.ts +0 -37
- package/dist/types/platforms/fortios/vdoms.d.ts +0 -50
- package/dist/types/platforms/index.d.ts +0 -13
- package/dist/types/platforms/junos/bgp.d.ts +0 -44
- package/dist/types/platforms/junos/index.d.ts +0 -114
- package/dist/types/platforms/junos/models.d.ts +0 -14
- package/dist/types/platforms/junos/render.d.ts +0 -154
- package/dist/types/platforms/line-diff.d.ts +0 -67
- package/dist/types/platforms/platform.d.ts +0 -135
- package/dist/types/platforms/raisecom-ros/index.d.ts +0 -71
- package/dist/types/platforms/raisecom-ros/models.d.ts +0 -14
- package/dist/types/platforms/raisecom-ros/render.d.ts +0 -41
- package/dist/types/platforms/routeros/export.d.ts +0 -59
- package/dist/types/platforms/routeros/index.d.ts +0 -116
- package/dist/types/platforms/routeros/models.d.ts +0 -18
- package/dist/types/platforms/routeros/plan.d.ts +0 -58
- package/dist/types/platforms/routeros/render.d.ts +0 -250
- package/dist/types/platforms/vrp/index.d.ts +0 -83
- package/dist/types/platforms/vrp/models.d.ts +0 -12
- package/dist/types/platforms/vrp/render.d.ts +0 -96
- package/dist/types/schema/common.d.ts +0 -30
- package/dist/types/schema/device.d.ts +0 -126
- package/dist/types/schema/firewall.d.ts +0 -131
- package/dist/types/schema/index.d.ts +0 -8
- package/dist/types/schema/interfaces.d.ts +0 -205
- package/dist/types/schema/policy.d.ts +0 -107
- package/dist/types/schema/routing.d.ts +0 -161
- package/dist/types/schema/system.d.ts +0 -198
- package/dist/types/schema/time-zones.d.ts +0 -2
- package/dist/types/transport/console.d.ts +0 -115
- package/dist/types/transport/path-check.d.ts +0 -32
- package/dist/types/transport/ssh.d.ts +0 -27
- package/src/cli/graph-format.ts +0 -91
- package/src/cli/new.ts +0 -234
- package/src/core/address-space.ts +0 -320
- package/src/core/cross-checks.ts +0 -369
- package/src/core/define.ts +0 -124
- package/src/core/project-files.ts +0 -35
- package/src/core/schema-docs.ts +0 -174
- package/src/core/validator.ts +0 -508
- package/src/helpers/bgp.ts +0 -473
- package/src/helpers/cables.ts +0 -74
- package/src/helpers/checks.ts +0 -201
- package/src/helpers/exchange.ts +0 -123
- package/src/helpers/filters.ts +0 -107
- package/src/helpers/index.ts +0 -25
- package/src/helpers/route-server.ts +0 -212
- package/src/helpers/upstreams.ts +0 -369
- package/src/index.ts +0 -13
- package/src/integrations/librenms.ts +0 -291
- package/src/integrations/netbox.ts +0 -620
- package/src/integrations/peeringdb.ts +0 -173
- package/src/integrations/prometheus.ts +0 -90
- package/src/schema/common.ts +0 -41
- package/src/schema/device.ts +0 -145
- package/src/schema/firewall.ts +0 -140
- package/src/schema/index.ts +0 -8
- package/src/schema/policy.ts +0 -115
- package/src/schema/routing.ts +0 -173
- package/src/schema/system.ts +0 -203
- package/src/studio/public/app.js +0 -186
- package/src/studio/public/compare.js +0 -250
- package/src/studio/public/dom.js +0 -138
- package/src/studio/public/evaluate.js +0 -687
- package/src/studio/public/export.js +0 -130
- package/src/studio/public/graph.js +0 -1541
- package/src/studio/public/index.html +0 -83
- package/src/studio/public/net.js +0 -134
- package/src/studio/public/tables.js +0 -1289
- package/src/studio/public/tools.js +0 -610
- package/templates/colocation/checks.ts +0 -53
- package/templates/colocation/circuit.config.ts +0 -9
- package/templates/colocation/edge-01.ts +0 -133
- package/templates/colocation/routing.ts +0 -13
- package/templates/colocation/tenancy.ts +0 -76
- package/templates/colocation/tenants/acme.ts +0 -19
- package/templates/colocation/tenants/index.ts +0 -6
- package/templates/colocation/tor-01.ts +0 -33
- package/templates/edge-router/checks.ts +0 -20
- package/templates/edge-router/circuit.config.ts +0 -8
- package/templates/edge-router/edge-01.ts +0 -110
- package/templates/edge-router/routing.ts +0 -13
- package/templates/project/AGENTS.md +0 -51
- package/templates/project/CLAUDE.md +0 -1
- package/templates/project/README.md +0 -16
- package/templates/project/gitignore +0 -5
- package/templates/project/tsconfig.json +0 -13
- package/templates/single-site/checks.ts +0 -26
- package/templates/single-site/circuit.config.ts +0 -8
- package/templates/single-site/router.ts +0 -86
- package/templates/single-site/site.ts +0 -18
- package/templates/single-site/switch.ts +0 -34
- /package/src/studio/{public → app/public}/favicon.svg +0 -0
- /package/src/studio/{public → app/public}/special-gothic-latin-ext.woff2 +0 -0
- /package/src/studio/{public → app/public}/special-gothic-latin.woff2 +0 -0
- /package/src/studio/{public → app/public}/special-gothic-license.txt +0 -0
package/src/core/secrets.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
// Secrets are references in the config and values only at the moment a command is sent. A reference names a variable, set in the environment or in
|
|
1
|
+
// Secrets are references in the config and values only at the moment a command is sent. A reference names a variable, set in the environment or in the network's secrets file (see `SecretsFile`), or a file kept out of the repository. A value in the secrets file is encrypted, or a 1Password reference, `op://vault/item/field`, read with the 1Password CLI.
|
|
2
2
|
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
4
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
4
|
import { join } from "node:path";
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
|
|
5
|
+
import type { Console, Device, IP, Secret } from "../model";
|
|
6
|
+
import { OnePassword, SecretsFile } from "./secrets-file";
|
|
7
|
+
|
|
8
|
+
/** Anything that holds secret references, such as a network's devices. */
|
|
9
|
+
type Network = object;
|
|
9
10
|
|
|
10
11
|
/** How a device is logged in to: its management address and SSH port, its connection user and that user's password, and its console. */
|
|
11
12
|
export type DeviceLogin = {
|
|
@@ -19,68 +20,59 @@ export type DeviceLogin = {
|
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
export class Secrets {
|
|
22
|
-
/** The directory of the config file.
|
|
23
|
+
/** The directory of the config file. The secrets file and secret files are found from here. */
|
|
23
24
|
private static root = process.cwd();
|
|
25
|
+
/** The secrets file of the network being worked on, relative to `root`. */
|
|
26
|
+
private static fileName = SecretsFile.DEFAULT;
|
|
24
27
|
|
|
25
|
-
private static
|
|
26
|
-
private static readonly fromOnePassword = new Map<string, string>();
|
|
28
|
+
private static values: Record<string, string> | undefined;
|
|
27
29
|
/** Every value read in this run, so `scrub` can hide it. */
|
|
28
30
|
private static readonly revealed = new Set<string>();
|
|
29
31
|
|
|
30
32
|
/** What `scrub` puts where a secret was. */
|
|
31
33
|
static readonly REDACTED = "<redacted>";
|
|
32
34
|
|
|
33
|
-
/** A value in
|
|
34
|
-
private static readonly
|
|
35
|
+
/** A value in plain text shorter than this is not hidden: hiding a short one would garble the text. */
|
|
36
|
+
private static readonly SHORTEST_PLAIN_VALUE = 6;
|
|
35
37
|
|
|
36
38
|
/** A marker anywhere in a command. Its group is the reference. */
|
|
37
39
|
static readonly MARKER = /<secret:([A-Za-z0-9_./:-]+)>/g;
|
|
38
40
|
|
|
39
|
-
/** Find
|
|
41
|
+
/** Find the secrets file and secret files from this directory, the config file's, instead of the working directory. */
|
|
40
42
|
static useRoot(directory: string): void {
|
|
41
43
|
Secrets.root = directory;
|
|
42
|
-
Secrets.
|
|
44
|
+
Secrets.useFile(SecretsFile.DEFAULT);
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const path = join(Secrets.root, ".env");
|
|
50
|
-
const text = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
51
|
-
|
|
52
|
-
Secrets.refuseCutValues(text);
|
|
53
|
-
Secrets.envFile = parseEnv(text) as Record<string, string>;
|
|
54
|
-
return Secrets.envFile;
|
|
47
|
+
/** Read secrets from this file, relative to the project: the one a network's `secrets.file` names, or `.circuit/secrets`. */
|
|
48
|
+
static useFile(name: string = SecretsFile.DEFAULT): void {
|
|
49
|
+
Secrets.fileName = name;
|
|
50
|
+
Secrets.values = undefined;
|
|
55
51
|
}
|
|
56
52
|
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const [, name, value] = /^\s*(?:export\s+)?([\w.-]+)\s*=\s*(.*)$/.exec(line) ?? [];
|
|
61
|
-
if (!name || !value || /^['"`]/.test(value) || !/\S#/.test(value)) continue;
|
|
62
|
-
|
|
63
|
-
throw new Error(`.env: the value of ${name} has a # in it, which starts a comment there; put the value in quotes`);
|
|
64
|
-
}
|
|
53
|
+
/** The secrets file in use. */
|
|
54
|
+
static file(): SecretsFile {
|
|
55
|
+
return SecretsFile.at(Secrets.root, Secrets.fileName);
|
|
65
56
|
}
|
|
66
57
|
|
|
67
|
-
/**
|
|
68
|
-
private static
|
|
69
|
-
|
|
70
|
-
if (cached !== undefined) return cached;
|
|
58
|
+
/** The secrets file's values as written, read once. A `#` starts a comment unless the value is quoted. */
|
|
59
|
+
private static stored(): Record<string, string> {
|
|
60
|
+
if (Secrets.values) return Secrets.values;
|
|
71
61
|
|
|
72
|
-
const
|
|
62
|
+
const file = Secrets.file();
|
|
63
|
+
Secrets.refuseCutValues(file.exists() ? readFileSync(file.path, "utf8") : "", file.name);
|
|
64
|
+
Secrets.values = file.values();
|
|
65
|
+
return Secrets.values;
|
|
66
|
+
}
|
|
73
67
|
|
|
74
|
-
|
|
68
|
+
/** An unquoted value is cut at a `#`. A `#` after a space is a comment, but one inside the value, as in `abc#def`, would cut a value short and send that to a device, so it stops the run and asks for quotes. */
|
|
69
|
+
private static refuseCutValues(text: string, name: string): void {
|
|
70
|
+
for (const line of text.split(/\r?\n/)) {
|
|
71
|
+
const [, key, value] = /^\s*(?:export\s+)?([\w.-]+)\s*=\s*(.*)$/.exec(line) ?? [];
|
|
72
|
+
if (!key || !value || /^['"`]/.test(value) || !/\S#/.test(value)) continue;
|
|
75
73
|
|
|
76
|
-
|
|
77
|
-
const reason = result.stderr.trim() || `op exited with ${result.status}`;
|
|
78
|
-
throw new Error(`secret ${name}: 1Password could not read ${reference}: ${reason}`);
|
|
74
|
+
throw new Error(`${name}: the value of ${key} has a # in it, which starts a comment there; put the value in quotes`);
|
|
79
75
|
}
|
|
80
|
-
|
|
81
|
-
const value = result.stdout;
|
|
82
|
-
Secrets.fromOnePassword.set(reference, value);
|
|
83
|
-
return value;
|
|
84
76
|
}
|
|
85
77
|
|
|
86
78
|
static isSecret(value: unknown): value is Secret {
|
|
@@ -92,18 +84,16 @@ export class Secrets {
|
|
|
92
84
|
return "secret" in secret ? secret.secret : `file:${secret.secret_file}`;
|
|
93
85
|
}
|
|
94
86
|
|
|
95
|
-
/** Where a reference's value comes from, such as `.
|
|
87
|
+
/** Where a reference's value comes from, such as `.circuit/secrets, encrypted` or `1Password, through the environment`, without reading it. Undefined when it is set nowhere. */
|
|
96
88
|
static sourceOf(reference: string): string | undefined {
|
|
97
89
|
if (reference.startsWith("file:")) return existsSync(join(Secrets.root, reference.slice("file:".length))) ? "a file" : undefined;
|
|
98
90
|
|
|
99
|
-
const
|
|
100
|
-
const value = process.env[reference] ||
|
|
91
|
+
const where = process.env[reference] ? "the environment" : Secrets.file().name;
|
|
92
|
+
const value = process.env[reference] || Secrets.stored()[reference];
|
|
101
93
|
if (!value) return undefined;
|
|
102
94
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return value.startsWith("op://") ? `1Password, through ${where}` : where;
|
|
95
|
+
if (value.startsWith("op://")) return `1Password, through ${where}`;
|
|
96
|
+
return SecretsFile.isEncrypted(value) ? `${where}, encrypted` : where;
|
|
107
97
|
}
|
|
108
98
|
|
|
109
99
|
/** The value behind a reference, as `referenceOf` writes it: one marker's reference, for a platform that takes a secret interactively. */
|
|
@@ -120,17 +110,26 @@ export class Secrets {
|
|
|
120
110
|
return value;
|
|
121
111
|
}
|
|
122
112
|
|
|
123
|
-
//
|
|
124
|
-
const fromFile = Secrets.
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
113
|
+
// The file is read first even when the environment has the value, so a value cut at a `#` is caught anyway.
|
|
114
|
+
const fromFile = Secrets.stored()[reference];
|
|
115
|
+
const fromEnvironment = process.env[reference];
|
|
116
|
+
if (!fromEnvironment && fromFile === undefined)
|
|
117
|
+
throw new Error(
|
|
118
|
+
`secret ${reference} is not set in ${Secrets.file().name}: set it with ${SecretsFile.command(Secrets.file().name, `set ${reference}`)}`
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const revealed = fromEnvironment
|
|
122
|
+
? fromEnvironment.startsWith("op://")
|
|
123
|
+
? OnePassword.read(fromEnvironment, `secret ${reference}`)
|
|
124
|
+
: fromEnvironment
|
|
125
|
+
: Secrets.file().decrypt(reference, fromFile!);
|
|
126
|
+
if (!revealed) throw new Error(`secret ${reference} is empty in ${Secrets.file().name}`);
|
|
127
127
|
|
|
128
|
-
const revealed = value.startsWith("op://") ? Secrets.readOnePassword(reference, value) : value;
|
|
129
128
|
Secrets.revealed.add(revealed);
|
|
130
129
|
return revealed;
|
|
131
130
|
}
|
|
132
131
|
|
|
133
|
-
/** The value of a secret, or of a variable named directly: from the environment,
|
|
132
|
+
/** The value of a secret, or of a variable named directly: from the environment, the secrets file, 1Password or a file. */
|
|
134
133
|
static reveal(secret: Secret | string): string {
|
|
135
134
|
return Secrets.valueOf(typeof secret === "string" ? secret : Secrets.referenceOf(secret));
|
|
136
135
|
}
|
|
@@ -144,16 +143,35 @@ export class Secrets {
|
|
|
144
143
|
return found;
|
|
145
144
|
}
|
|
146
145
|
|
|
146
|
+
/**
|
|
147
|
+
* The secrets file when a secret of `value`, such as a device, is encrypted in it and no password is at hand: the person has to type
|
|
148
|
+
* it before the work starts. Undefined when every secret can be read. A password in the environment or 1Password opens the file here.
|
|
149
|
+
*/
|
|
150
|
+
static lockedFor(value: unknown): SecretsFile | undefined {
|
|
151
|
+
const file = Secrets.file();
|
|
152
|
+
const needed = [...Secrets.referencesIn(value)].some(
|
|
153
|
+
(reference) =>
|
|
154
|
+
!reference.startsWith("file:") && !process.env[reference] && SecretsFile.isEncrypted(Secrets.stored()[reference] ?? "")
|
|
155
|
+
);
|
|
156
|
+
return needed && !file.tryUnlock() ? file : undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
147
159
|
/**
|
|
148
160
|
* Text with every secret value Circuit knows replaced by `<redacted>`, before it is printed. Devices echo what they are sent, passwords too.
|
|
149
161
|
*
|
|
150
|
-
* Hidden are: each value in
|
|
162
|
+
* Hidden are: each value in plain text in the secrets file of at least six characters, each value read in this run (1Password reads and secret files among them), each secret of `network` that the environment, the secrets file or a file holds, and `extra`. A 1Password reference not read in this run is not read for this.
|
|
151
163
|
*/
|
|
152
164
|
static scrub(text: string, options: { network?: Network; extra?: readonly string[] } = {}): string {
|
|
153
165
|
const values = new Set<string>(options.extra ?? []);
|
|
154
166
|
|
|
155
|
-
|
|
156
|
-
|
|
167
|
+
// An encrypted value or a reference is not the secret itself.
|
|
168
|
+
for (const [name, value] of Object.entries(Secrets.stored())) {
|
|
169
|
+
if (
|
|
170
|
+
name !== SecretsFile.PASSWORD &&
|
|
171
|
+
value.length >= Secrets.SHORTEST_PLAIN_VALUE &&
|
|
172
|
+
SecretsFile.kindOf(value) === "not encrypted"
|
|
173
|
+
)
|
|
174
|
+
values.add(value);
|
|
157
175
|
}
|
|
158
176
|
|
|
159
177
|
for (const value of Secrets.revealed) values.add(value);
|
|
@@ -168,22 +186,23 @@ export class Secrets {
|
|
|
168
186
|
return ordered.reduce((out, value) => out.split(value).join(Secrets.REDACTED), text);
|
|
169
187
|
}
|
|
170
188
|
|
|
171
|
-
/** The value behind a reference when it can be had without asking anyone: a 1Password reference only once read in this run. Undefined otherwise. */
|
|
189
|
+
/** The value behind a reference when it can be had without asking anyone: a 1Password reference only once read in this run, an encrypted value only with the file open. Undefined otherwise. */
|
|
172
190
|
private static storedValueOf(reference: string): string | undefined {
|
|
173
191
|
if (reference.startsWith("file:")) {
|
|
174
192
|
const path = join(Secrets.root, reference.slice("file:".length));
|
|
175
193
|
return existsSync(path) ? readFileSync(path, "utf8").trimEnd() : undefined;
|
|
176
194
|
}
|
|
177
195
|
|
|
178
|
-
const
|
|
179
|
-
if (
|
|
196
|
+
const fromEnvironment = process.env[reference];
|
|
197
|
+
if (fromEnvironment) return fromEnvironment.startsWith("op://") ? OnePassword.cached(fromEnvironment) : fromEnvironment;
|
|
180
198
|
|
|
181
|
-
|
|
199
|
+
const value = Secrets.stored()[reference];
|
|
200
|
+
return value ? Secrets.file().revealIfOpen(reference, value) : undefined;
|
|
182
201
|
}
|
|
183
202
|
|
|
184
203
|
/** How Circuit logs in to a device: its address, port, connection user, that user's password revealed, and its console. */
|
|
185
204
|
static loginOf(device: Device): DeviceLogin {
|
|
186
|
-
const user = device.users?.[device.connection.user];
|
|
205
|
+
const user = device.system?.users?.[device.connection.user];
|
|
187
206
|
if (!user) throw new Error(`${device.name}: connection user ${device.connection.user} is not one of its users`);
|
|
188
207
|
|
|
189
208
|
return {
|