@ratelkey/burrow-sdk 0.1.0

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.
Files changed (58) hide show
  1. package/README.md +108 -0
  2. package/dist/cjs/Burrow.js +30 -0
  3. package/dist/cjs/Burrow.js.map +1 -0
  4. package/dist/cjs/BurrowClient.js +110 -0
  5. package/dist/cjs/BurrowClient.js.map +1 -0
  6. package/dist/cjs/Errors.js +80 -0
  7. package/dist/cjs/Errors.js.map +1 -0
  8. package/dist/cjs/Identity.js +274 -0
  9. package/dist/cjs/Identity.js.map +1 -0
  10. package/dist/cjs/Model.js +143 -0
  11. package/dist/cjs/Model.js.map +1 -0
  12. package/dist/cjs/Signer.js +42 -0
  13. package/dist/cjs/Signer.js.map +1 -0
  14. package/dist/cjs/Tls.js +85 -0
  15. package/dist/cjs/Tls.js.map +1 -0
  16. package/dist/cjs/Transport.js +198 -0
  17. package/dist/cjs/Transport.js.map +1 -0
  18. package/dist/cjs/index.js +28 -0
  19. package/dist/cjs/index.js.map +1 -0
  20. package/dist/cjs/package.json +3 -0
  21. package/dist/esm/Burrow.d.ts +59 -0
  22. package/dist/esm/Burrow.d.ts.map +1 -0
  23. package/dist/esm/Burrow.js +27 -0
  24. package/dist/esm/Burrow.js.map +1 -0
  25. package/dist/esm/BurrowClient.d.ts +40 -0
  26. package/dist/esm/BurrowClient.d.ts.map +1 -0
  27. package/dist/esm/BurrowClient.js +106 -0
  28. package/dist/esm/BurrowClient.js.map +1 -0
  29. package/dist/esm/Errors.d.ts +57 -0
  30. package/dist/esm/Errors.d.ts.map +1 -0
  31. package/dist/esm/Errors.js +67 -0
  32. package/dist/esm/Errors.js.map +1 -0
  33. package/dist/esm/Identity.d.ts +62 -0
  34. package/dist/esm/Identity.d.ts.map +1 -0
  35. package/dist/esm/Identity.js +269 -0
  36. package/dist/esm/Identity.js.map +1 -0
  37. package/dist/esm/Model.d.ts +75 -0
  38. package/dist/esm/Model.d.ts.map +1 -0
  39. package/dist/esm/Model.js +137 -0
  40. package/dist/esm/Model.js.map +1 -0
  41. package/dist/esm/Signer.d.ts +19 -0
  42. package/dist/esm/Signer.d.ts.map +1 -0
  43. package/dist/esm/Signer.js +39 -0
  44. package/dist/esm/Signer.js.map +1 -0
  45. package/dist/esm/Tls.d.ts +71 -0
  46. package/dist/esm/Tls.d.ts.map +1 -0
  47. package/dist/esm/Tls.js +79 -0
  48. package/dist/esm/Tls.js.map +1 -0
  49. package/dist/esm/Transport.d.ts +29 -0
  50. package/dist/esm/Transport.d.ts.map +1 -0
  51. package/dist/esm/Transport.js +195 -0
  52. package/dist/esm/Transport.js.map +1 -0
  53. package/dist/esm/index.d.ts +9 -0
  54. package/dist/esm/index.d.ts.map +1 -0
  55. package/dist/esm/index.js +6 -0
  56. package/dist/esm/index.js.map +1 -0
  57. package/dist/esm/package.json +3 -0
  58. package/package.json +1 -0
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProjectAccess = exports.SecretCoordinate = exports.Secret = void 0;
4
+ const Errors_js_1 = require("./Errors.js");
5
+ /**
6
+ * A secret read from a Burrow — fetched whole in a single signed call. Project it locally (no re-fetch):
7
+ *
8
+ * ```ts
9
+ * const db = await burrow.getSecret("payments/prod/DATABASE");
10
+ * db.value; // a single-value secret
11
+ * db.toMap(); // every field as name → value
12
+ * db.getField("password"); // one field
13
+ * const [user, pass] = db.getFields("username", "password"); // in order — destructures
14
+ * ```
15
+ *
16
+ * A shape mismatch (e.g. `value` on a structured secret, or `toMap()` on a single one) throws
17
+ * {@link SecretShapeException}; a missing field throws {@link FieldNotFoundException}.
18
+ */
19
+ class Secret {
20
+ name;
21
+ singleValue;
22
+ singleType;
23
+ singleVersion;
24
+ fieldsByName;
25
+ constructor(
26
+ /** The secret's name (the final segment of its `project/env/NAME` coordinate). */
27
+ name, singleValue, singleType, singleVersion, fieldsByName) {
28
+ this.name = name;
29
+ this.singleValue = singleValue;
30
+ this.singleType = singleType;
31
+ this.singleVersion = singleVersion;
32
+ this.fieldsByName = fieldsByName;
33
+ }
34
+ /** True when this is a structured (multi-field) secret rather than a single value. */
35
+ get isStructured() {
36
+ return this.fieldsByName !== null;
37
+ }
38
+ /** The single value. Throws {@link SecretShapeException} if this is a structured secret. */
39
+ get value() {
40
+ if (this.singleValue === null) {
41
+ throw new Errors_js_1.SecretShapeException(`'${this.name}' is a structured secret; use toMap(), getField(), or getFields()`);
42
+ }
43
+ return this.singleValue;
44
+ }
45
+ /** The declared type of a single-value secret; null for a structured one. */
46
+ get type() {
47
+ return this.singleType;
48
+ }
49
+ /** The current version of a single-value secret; null for a structured one. */
50
+ get version() {
51
+ return this.singleVersion;
52
+ }
53
+ /** Every field as a name → value map. Throws {@link SecretShapeException} if this is a single-value secret. */
54
+ toMap() {
55
+ const out = {};
56
+ for (const [name, field] of this.structured())
57
+ out[name] = field.value;
58
+ return out;
59
+ }
60
+ /** The typed fields. Throws {@link SecretShapeException} if this is a single-value secret. */
61
+ fields() {
62
+ return [...this.structured().values()];
63
+ }
64
+ /**
65
+ * One field's value. Throws {@link SecretShapeException} if single-value, or
66
+ * {@link FieldNotFoundException} if absent.
67
+ */
68
+ getField(field) {
69
+ const f = this.structured();
70
+ const hit = f.get(field);
71
+ if (hit === undefined)
72
+ throw new Errors_js_1.FieldNotFoundException(this.name, field, f.keys());
73
+ return hit.value;
74
+ }
75
+ /**
76
+ * The named fields' values, in the order given — for destructuring:
77
+ * `const [user, pass] = secret.getFields("username", "password")`. Throws {@link SecretShapeException} if
78
+ * single-value, or {@link FieldNotFoundException} if any name is absent.
79
+ */
80
+ getFields(...names) {
81
+ const f = this.structured();
82
+ return names.map((n) => {
83
+ const hit = f.get(n);
84
+ if (hit === undefined)
85
+ throw new Errors_js_1.FieldNotFoundException(this.name, n, f.keys());
86
+ return hit.value;
87
+ });
88
+ }
89
+ structured() {
90
+ if (this.fieldsByName === null) {
91
+ throw new Errors_js_1.SecretShapeException(`'${this.name}' is a single-value secret; use value`);
92
+ }
93
+ return this.fieldsByName;
94
+ }
95
+ /** @internal */
96
+ static single(name, value, type, version) {
97
+ return new Secret(name, value, type, version, null);
98
+ }
99
+ /** @internal */
100
+ static structured(name, fields) {
101
+ return new Secret(name, null, null, null, fields);
102
+ }
103
+ }
104
+ exports.Secret = Secret;
105
+ /** A secret this machine can read, addressed by its `project / env / name` coordinate. */
106
+ class SecretCoordinate {
107
+ project;
108
+ environment;
109
+ name;
110
+ constructor(project, environment, name) {
111
+ this.project = project;
112
+ this.environment = environment;
113
+ this.name = name;
114
+ }
115
+ /** The `project/env/NAME` slug form, as the Burrow lists it and `BurrowClient.getSecret` takes it. */
116
+ toString() {
117
+ return `${this.project}/${this.environment}/${this.name}`;
118
+ }
119
+ /** Parse a `project/env/NAME` slug into its parts. */
120
+ static parse(slug) {
121
+ // Split into at most three parts: a secret name may legitimately contain a slash, the coordinate's
122
+ // first two separators may not.
123
+ const first = slug.indexOf("/");
124
+ if (first < 0)
125
+ return new SecretCoordinate(slug, "", "");
126
+ const second = slug.indexOf("/", first + 1);
127
+ if (second < 0)
128
+ return new SecretCoordinate(slug.slice(0, first), slug.slice(first + 1), "");
129
+ return new SecretCoordinate(slug.slice(0, first), slug.slice(first + 1, second), slug.slice(second + 1));
130
+ }
131
+ }
132
+ exports.SecretCoordinate = SecretCoordinate;
133
+ /** A project this machine can read, and the environment categories it holds a grant for. */
134
+ class ProjectAccess {
135
+ project;
136
+ categories;
137
+ constructor(project, categories) {
138
+ this.project = project;
139
+ this.categories = categories;
140
+ }
141
+ }
142
+ exports.ProjectAccess = ProjectAccess;
143
+ //# sourceMappingURL=Model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Model.js","sourceRoot":"","sources":["../../src/Model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MachineSigner = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ /**
6
+ * Builds the Ed25519-signed `Ratel-*` headers for a machine-plane request. The canonical string MUST
7
+ * byte-match the Burrow's `MachineSignature.canonical`, or every read is rejected 401:
8
+ *
9
+ * ```
10
+ * ACTION \n resource[0] \n … \n resource[n] \n TIMESTAMP \n NONCE \n CLIENT (no trailing newline)
11
+ * ```
12
+ *
13
+ * TIMESTAMP is unix millis as a decimal string; the SAME string goes in the header and the signed bytes. A
14
+ * fresh timestamp + nonce per call makes a captured request unreplayable (the Burrow enforces a tight
15
+ * timestamp window and a single-use nonce).
16
+ */
17
+ exports.MachineSigner = {
18
+ canonical(action, resource, timestamp, nonce, client) {
19
+ let out = `${action}\n`;
20
+ for (const part of resource)
21
+ out += `${part}\n`;
22
+ out += `${timestamp}\n${nonce}\n${client}`;
23
+ return Buffer.from(out, "utf8");
24
+ },
25
+ /** The signed `Ratel-*` headers for one request. */
26
+ sign(machineId, key, action, resource, client, nowMillis) {
27
+ const timestamp = String(nowMillis);
28
+ // Standard base64 with padding — the Burrow decodes with java.util.Base64's standard decoder. 16 bytes
29
+ // encodes to 24 characters, well inside the Burrow's 64-character nonce bound.
30
+ const nonce = (0, node_crypto_1.randomBytes)(16).toString("base64");
31
+ // Ed25519 signs the message directly: the digest algorithm must be null, not a hash name.
32
+ const signature = (0, node_crypto_1.sign)(null, exports.MachineSigner.canonical(action, resource, timestamp, nonce, client), key).toString("base64");
33
+ return {
34
+ "Ratel-Machine": machineId,
35
+ "Ratel-Timestamp": timestamp,
36
+ "Ratel-Nonce": nonce,
37
+ "Ratel-Signature": signature,
38
+ "Ratel-Client": client,
39
+ };
40
+ },
41
+ };
42
+ //# sourceMappingURL=Signer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Signer.js","sourceRoot":"","sources":["../../src/Signer.ts"],"names":[],"mappings":";;;AAAA,6CAA6E;AAE7E;;;;;;;;;;;GAWG;AACU,QAAA,aAAa,GAAG;IAC3B,SAAS,CACP,MAAc,EACd,QAA2B,EAC3B,SAAiB,EACjB,KAAa,EACb,MAAc;QAEd,IAAI,GAAG,GAAG,GAAG,MAAM,IAAI,CAAC;QACxB,KAAK,MAAM,IAAI,IAAI,QAAQ;YAAE,GAAG,IAAI,GAAG,IAAI,IAAI,CAAC;QAChD,GAAG,IAAI,GAAG,SAAS,KAAK,KAAK,KAAK,MAAM,EAAE,CAAC;QAC3C,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAClC,CAAC;IAED,oDAAoD;IACpD,IAAI,CACF,SAAiB,EACjB,GAAc,EACd,MAAc,EACd,QAA2B,EAC3B,MAAc,EACd,SAAiB;QAEjB,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QACpC,uGAAuG;QACvG,+EAA+E;QAC/E,MAAM,KAAK,GAAG,IAAA,yBAAW,EAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACjD,0FAA0F;QAC1F,MAAM,SAAS,GAAG,IAAA,kBAAS,EACzB,IAAI,EACJ,qBAAa,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,EACnE,GAAG,CACJ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACrB,OAAO;YACL,eAAe,EAAE,SAAS;YAC1B,iBAAiB,EAAE,SAAS;YAC5B,aAAa,EAAE,KAAK;YACpB,iBAAiB,EAAE,SAAS;YAC5B,cAAc,EAAE,MAAM;SACvB,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PinStore = exports.TlsMode = void 0;
4
+ exports.publicKeyPin = publicKeyPin;
5
+ exports.fingerprintsEqual = fingerprintsEqual;
6
+ const node_crypto_1 = require("node:crypto");
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = require("node:path");
9
+ /** The three trust modes, as values: `TlsMode.Pinned`, `TlsMode.PinnedTo("aa:bb:…")`, `TlsMode.SystemTrust`. */
10
+ exports.TlsMode = {
11
+ Pinned: { kind: "pinned" },
12
+ PinnedTo: (fingerprint) => ({ kind: "pinnedTo", fingerprint }),
13
+ SystemTrust: { kind: "systemTrust" },
14
+ };
15
+ /**
16
+ * SHA-256 of a certificate's PUBLIC KEY (its SubjectPublicKeyInfo) as lowercase colon-separated hex — the
17
+ * form the Burrow shows in Settings → Network and carries in a bootstrap code.
18
+ *
19
+ * The key rather than the whole certificate. A Burrow's certificate legitimately changes — a custom domain
20
+ * added to its SANs, an ACME renewal, a refresh — while the Burrow itself is the same one. Pinning the
21
+ * certificate turned each of those into "this is a different server", and a machine that had done nothing
22
+ * wrong stopped being able to read secrets.
23
+ *
24
+ * Parsed through `X509Certificate` and exported as SPKI DER rather than read off `getPeerCertificate().pubkey`,
25
+ * because that field's encoding is not documented to be SPKI and a pin has to hash exactly what the Burrow
26
+ * hashed.
27
+ */
28
+ function publicKeyPin(der) {
29
+ const spki = new node_crypto_1.X509Certificate(Buffer.from(der)).publicKey.export({ type: "spki", format: "der" });
30
+ const hex = (0, node_crypto_1.createHash)("sha256").update(spki).digest("hex");
31
+ return (hex.match(/../g) ?? []).join(":");
32
+ }
33
+ /** Compare two fingerprints ignoring case and separators, so "AA:BB…" and "aabb…" match. */
34
+ function fingerprintsEqual(a, b) {
35
+ const norm = (s) => s.replace(/[^A-Za-z0-9]/g, "").toLowerCase();
36
+ const na = norm(a);
37
+ return na.length > 0 && na === norm(b);
38
+ }
39
+ /**
40
+ * One Burrow's pinned key, at `<identity dir>/known_cert`, shared with the `ratel` CLI.
41
+ *
42
+ * Per burrow rather than one map for the machine. The pin and the identity are the same trust relationship:
43
+ * removing a Burrow should forget it entirely, and a shared file left the pin behind for a Burrow that no
44
+ * longer had an identity. It also means two Burrows can never contend for one file.
45
+ *
46
+ * A single value, not a map keyed by URL: the pin is the Burrow's KEY, so the same Burrow reached by its LAN
47
+ * address, its public address or its relay hostname presents the same one.
48
+ *
49
+ * Reads tolerate a missing or unreadable file (treated as unpinned); writes are atomic (temp file + rename)
50
+ * so concurrent SDK/CLI processes can't tear it. A null directory keeps the pin in memory only.
51
+ */
52
+ class PinStore {
53
+ file;
54
+ constructor(dir) {
55
+ this.file = dir === null ? null : (0, node_path_1.join)(dir, "known_cert");
56
+ }
57
+ read() {
58
+ if (this.file === null)
59
+ return null;
60
+ try {
61
+ return (0, node_fs_1.readFileSync)(this.file, "utf8").trim() || null;
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }
67
+ write(pin) {
68
+ if (this.file === null)
69
+ return;
70
+ if (this.read() === pin)
71
+ return;
72
+ // Best-effort: a read-only home just means the next process re-pins. Never fail a read over it.
73
+ try {
74
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(this.file), { recursive: true });
75
+ const tmp = `${this.file}.tmp`;
76
+ (0, node_fs_1.writeFileSync)(tmp, `${pin}\n`);
77
+ (0, node_fs_1.renameSync)(tmp, this.file);
78
+ }
79
+ catch {
80
+ // ignored
81
+ }
82
+ }
83
+ }
84
+ exports.PinStore = PinStore;
85
+ //# sourceMappingURL=Tls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tls.js","sourceRoot":"","sources":["../../src/Tls.ts"],"names":[],"mappings":";;;AA+CA,oCAIC;AAGD,8CAIC;AA1DD,6CAA0D;AAC1D,qCAA6E;AAC7E,yCAA0C;AAyB1C,gHAAgH;AACnG,QAAA,OAAO,GAAG;IACrB,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAa;IACrC,QAAQ,EAAE,CAAC,WAAmB,EAAW,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;IAC/E,WAAW,EAAE,EAAE,IAAI,EAAE,aAAa,EAAa;CACvC,CAAC;AAEX;;;;;;;;;;;;GAYG;AACH,SAAgB,YAAY,CAAC,GAAe;IAC1C,MAAM,IAAI,GAAG,IAAI,6BAAe,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACrG,MAAM,GAAG,GAAG,IAAA,wBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5C,CAAC;AAED,4FAA4F;AAC5F,SAAgB,iBAAiB,CAAC,CAAS,EAAE,CAAS;IACpD,MAAM,IAAI,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACjF,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACnB,OAAO,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAa,QAAQ;IACF,IAAI,CAAgB;IAErC,YAAY,GAAkB;QAC5B,IAAI,CAAC,IAAI,GAAG,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAA,gBAAI,EAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAC5D,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QACpC,IAAI,CAAC;YACH,OAAO,IAAA,sBAAY,EAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAW;QACf,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;YAAE,OAAO;QAC/B,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG;YAAE,OAAO;QAChC,gGAAgG;QAChG,IAAI,CAAC;YACH,IAAA,mBAAS,EAAC,IAAA,mBAAO,EAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACnD,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,MAAM,CAAC;YAC/B,IAAA,uBAAa,EAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,CAAC;YAC/B,IAAA,oBAAU,EAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,UAAU;QACZ,CAAC;IACH,CAAC;CACF;AA7BD,4BA6BC"}
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Transport = void 0;
4
+ const node_http_1 = require("node:http");
5
+ const node_net_1 = require("node:net");
6
+ const node_tls_1 = require("node:tls");
7
+ const Errors_js_1 = require("./Errors.js");
8
+ const Tls_js_1 = require("./Tls.js");
9
+ const TIMEOUT_MS = 15_000;
10
+ /**
11
+ * Performs signed machine-plane GETs against one Burrow, applying the configured {@link TlsMode}. Burrows
12
+ * serve HTTPS self-signed by default, so `TlsMode.Pinned` pins the certificate trust-on-first-use and
13
+ * refuses a changed one. Maps the Burrow's HTTP status + `{"error": …}` body to the typed
14
+ * {@link BurrowException}s, and surfaces the Burrow's own rejection reason verbatim.
15
+ *
16
+ * The TLS handshake is completed and the pin checked BEFORE the HTTP request is handed the socket, so a
17
+ * mismatched certificate never sees the signed `Ratel-*` headers. That ordering is the whole point of
18
+ * pinning, and it is why this dials the socket itself rather than letting the HTTP agent do it.
19
+ */
20
+ class Transport {
21
+ tls;
22
+ base;
23
+ pins;
24
+ /** The fingerprint currently enforced. Null in trust-on-first-use mode until the first pin is learned. */
25
+ pinned;
26
+ constructor(burrowUrl, tls,
27
+ /** This Burrow's identity directory — where its pinned key lives, beside the key that signs for it. */
28
+ identityDir) {
29
+ this.tls = tls;
30
+ this.base = burrowUrl.replace(/\/+$/, "");
31
+ this.pins = new Tls_js_1.PinStore(identityDir);
32
+ this.pinned = tls.kind === "pinnedTo" ? tls.fingerprint : tls.kind === "pinned" ? this.pins.read() : null;
33
+ }
34
+ /** Signed GET, returning the response body on 2xx; throws a typed {@link BurrowException} otherwise. */
35
+ async get(path, headers) {
36
+ const url = this.resolve(path);
37
+ const conn = await this.connect(url);
38
+ let status;
39
+ let body;
40
+ try {
41
+ ({ status, body } = await exchange(conn.socket, url, headers));
42
+ }
43
+ catch (e) {
44
+ throw new Errors_js_1.BurrowTransportException(`reaching burrow ${this.base}: ${messageOf(e)}`, { cause: e });
45
+ }
46
+ finally {
47
+ conn.socket.destroy();
48
+ }
49
+ if (status < 200 || status > 299)
50
+ throw this.errorFor(status, body);
51
+ this.commitPinIfNew(conn.fingerprint);
52
+ return body;
53
+ }
54
+ resolve(path) {
55
+ let url;
56
+ try {
57
+ url = new URL(this.base + path);
58
+ }
59
+ catch (e) {
60
+ throw new Errors_js_1.BurrowTransportException(`invalid burrow URL '${this.base}${path}': ${messageOf(e)}`, { cause: e });
61
+ }
62
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
63
+ throw new Errors_js_1.BurrowTransportException(`burrow URL '${this.base}' is not http(s)`);
64
+ }
65
+ return url;
66
+ }
67
+ /** Dial the Burrow and, for the pinned modes, verify its certificate before anything is written. */
68
+ connect(url) {
69
+ const host = url.hostname;
70
+ const port = url.port.length > 0 ? Number(url.port) : url.protocol === "https:" ? 443 : 80;
71
+ // Pinning is authoritative for a self-signed Burrow, so the CA chain and hostname checks are waived —
72
+ // the certificate is reached by IP or a self-signed domain that no SAN or CA would match. SystemTrust
73
+ // keeps the full standard verification and needs no pin check of its own.
74
+ const pinning = this.tls.kind !== "systemTrust";
75
+ const socket = url.protocol === "http:"
76
+ ? (0, node_net_1.connect)({ host, port })
77
+ : (0, node_tls_1.connect)({
78
+ host,
79
+ port,
80
+ // SNI carries a hostname; an IP literal is not a legal server name.
81
+ ...((0, node_net_1.isIP)(host) === 0 ? { servername: host } : {}),
82
+ rejectUnauthorized: !pinning,
83
+ });
84
+ return new Promise((resolve, reject) => {
85
+ const finish = (settle) => {
86
+ socket.removeListener("error", onError);
87
+ socket.removeListener("timeout", onTimeout);
88
+ socket.removeListener("secureConnect", onSecure);
89
+ socket.removeListener("connect", onPlain);
90
+ socket.setTimeout(0);
91
+ // Hold a no-op error listener so a fault between here and the HTTP client taking the socket over is
92
+ // never an unhandled 'error' event, which would take the process down.
93
+ socket.on("error", ignore);
94
+ settle();
95
+ };
96
+ const onError = (e) => finish(() => reject(new Errors_js_1.BurrowTransportException(`reaching burrow ${this.base}: ${e.message}`, { cause: e })));
97
+ const onTimeout = () => {
98
+ socket.destroy();
99
+ finish(() => reject(new Errors_js_1.BurrowTransportException(`reaching burrow ${this.base}: timed out after ${TIMEOUT_MS}ms`)));
100
+ };
101
+ const onPlain = () => finish(() => resolve({ socket, fingerprint: null }));
102
+ const onSecure = () => {
103
+ if (!pinning) {
104
+ finish(() => resolve({ socket, fingerprint: null }));
105
+ return;
106
+ }
107
+ const der = socket.getPeerCertificate().raw;
108
+ if (der === undefined || der.length === 0) {
109
+ socket.destroy();
110
+ finish(() => reject(new Errors_js_1.BurrowCertificateException("Burrow presented no certificate")));
111
+ return;
112
+ }
113
+ const seen = (0, Tls_js_1.publicKeyPin)(der);
114
+ const pin = this.pinned;
115
+ if (pin !== null && !(0, Tls_js_1.fingerprintsEqual)(seen, pin)) {
116
+ socket.destroy();
117
+ finish(() => reject(new Errors_js_1.BurrowCertificateException(`Burrow key does not match the pinned one (pinned=${pin}, got=${seen})`)));
118
+ return;
119
+ }
120
+ finish(() => resolve({ socket, fingerprint: seen }));
121
+ };
122
+ socket.setTimeout(TIMEOUT_MS);
123
+ socket.once("timeout", onTimeout);
124
+ socket.once("error", onError);
125
+ if (url.protocol === "https:")
126
+ socket.once("secureConnect", onSecure);
127
+ else
128
+ socket.once("connect", onPlain);
129
+ });
130
+ }
131
+ commitPinIfNew(fingerprint) {
132
+ if (fingerprint === null)
133
+ return;
134
+ if (this.tls.kind !== "pinned")
135
+ return; // only TOFU persists; PinnedTo is caller-fixed, never learned
136
+ if (this.pinned !== null)
137
+ return;
138
+ this.pins.write(fingerprint);
139
+ this.pinned = fingerprint; // enforce on subsequent requests within this process too
140
+ }
141
+ errorFor(status, body) {
142
+ let reason = "";
143
+ try {
144
+ const parsed = JSON.parse(body);
145
+ if (parsed !== null && typeof parsed === "object") {
146
+ const e = parsed.error;
147
+ if (typeof e === "string")
148
+ reason = e;
149
+ }
150
+ }
151
+ catch {
152
+ // Not JSON — fall back to the raw body below.
153
+ }
154
+ if (reason.trim().length === 0)
155
+ reason = body.trim().length > 0 ? body.trim() : "(no message)";
156
+ switch (status) {
157
+ case 401:
158
+ return new Errors_js_1.BurrowAuthException(reason);
159
+ case 403:
160
+ return new Errors_js_1.BurrowForbiddenException(reason);
161
+ case 404:
162
+ return new Errors_js_1.BurrowNotFoundException(reason);
163
+ default:
164
+ return new Errors_js_1.BurrowServerException(status, reason);
165
+ }
166
+ }
167
+ }
168
+ exports.Transport = Transport;
169
+ /** Write the GET over an already-connected (and, for HTTPS, already-verified) socket and read the response. */
170
+ function exchange(socket, url, headers) {
171
+ return new Promise((resolve, reject) => {
172
+ const req = (0, node_http_1.request)({
173
+ method: "GET",
174
+ // No agent, and createConnection hands back the socket we already dialled — host/port here only shape
175
+ // the request line, nothing is dialled again.
176
+ createConnection: () => socket,
177
+ host: url.hostname,
178
+ path: url.pathname + url.search,
179
+ headers: { ...headers, Accept: "application/json", Host: url.host },
180
+ });
181
+ req.setTimeout(TIMEOUT_MS, () => req.destroy(new Error(`timed out after ${TIMEOUT_MS}ms`)));
182
+ req.on("error", reject);
183
+ req.on("response", (res) => {
184
+ const chunks = [];
185
+ res.on("data", (chunk) => chunks.push(chunk));
186
+ res.on("error", reject);
187
+ res.on("end", () => resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf8") }));
188
+ });
189
+ req.end();
190
+ });
191
+ }
192
+ function ignore() {
193
+ // Deliberately empty: see the note in Transport.connect.
194
+ }
195
+ function messageOf(e) {
196
+ return e instanceof Error ? e.message : String(e);
197
+ }
198
+ //# sourceMappingURL=Transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Transport.js","sourceRoot":"","sources":["../../src/Transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SecretShapeException = exports.FieldNotFoundException = exports.BurrowTransportException = exports.BurrowServerException = exports.BurrowNotFoundException = exports.BurrowIdentityException = exports.BurrowForbiddenException = exports.BurrowException = exports.BurrowCertificateException = exports.BurrowAuthException = exports.TlsMode = exports.MachineIdentity = exports.Identities = exports.SecretCoordinate = exports.Secret = exports.ProjectAccess = exports.SDK_VERSION = exports.DEFAULT_CLIENT_TAG = exports.Burrow = void 0;
4
+ var Burrow_js_1 = require("./Burrow.js");
5
+ Object.defineProperty(exports, "Burrow", { enumerable: true, get: function () { return Burrow_js_1.Burrow; } });
6
+ Object.defineProperty(exports, "DEFAULT_CLIENT_TAG", { enumerable: true, get: function () { return Burrow_js_1.DEFAULT_CLIENT_TAG; } });
7
+ Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return Burrow_js_1.SDK_VERSION; } });
8
+ var Model_js_1 = require("./Model.js");
9
+ Object.defineProperty(exports, "ProjectAccess", { enumerable: true, get: function () { return Model_js_1.ProjectAccess; } });
10
+ Object.defineProperty(exports, "Secret", { enumerable: true, get: function () { return Model_js_1.Secret; } });
11
+ Object.defineProperty(exports, "SecretCoordinate", { enumerable: true, get: function () { return Model_js_1.SecretCoordinate; } });
12
+ var Identity_js_1 = require("./Identity.js");
13
+ Object.defineProperty(exports, "Identities", { enumerable: true, get: function () { return Identity_js_1.Identities; } });
14
+ Object.defineProperty(exports, "MachineIdentity", { enumerable: true, get: function () { return Identity_js_1.MachineIdentity; } });
15
+ var Tls_js_1 = require("./Tls.js");
16
+ Object.defineProperty(exports, "TlsMode", { enumerable: true, get: function () { return Tls_js_1.TlsMode; } });
17
+ var Errors_js_1 = require("./Errors.js");
18
+ Object.defineProperty(exports, "BurrowAuthException", { enumerable: true, get: function () { return Errors_js_1.BurrowAuthException; } });
19
+ Object.defineProperty(exports, "BurrowCertificateException", { enumerable: true, get: function () { return Errors_js_1.BurrowCertificateException; } });
20
+ Object.defineProperty(exports, "BurrowException", { enumerable: true, get: function () { return Errors_js_1.BurrowException; } });
21
+ Object.defineProperty(exports, "BurrowForbiddenException", { enumerable: true, get: function () { return Errors_js_1.BurrowForbiddenException; } });
22
+ Object.defineProperty(exports, "BurrowIdentityException", { enumerable: true, get: function () { return Errors_js_1.BurrowIdentityException; } });
23
+ Object.defineProperty(exports, "BurrowNotFoundException", { enumerable: true, get: function () { return Errors_js_1.BurrowNotFoundException; } });
24
+ Object.defineProperty(exports, "BurrowServerException", { enumerable: true, get: function () { return Errors_js_1.BurrowServerException; } });
25
+ Object.defineProperty(exports, "BurrowTransportException", { enumerable: true, get: function () { return Errors_js_1.BurrowTransportException; } });
26
+ Object.defineProperty(exports, "FieldNotFoundException", { enumerable: true, get: function () { return Errors_js_1.FieldNotFoundException; } });
27
+ Object.defineProperty(exports, "SecretShapeException", { enumerable: true, get: function () { return Errors_js_1.SecretShapeException; } });
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,yCAAsE;AAA7D,mGAAA,MAAM,OAAA;AAAE,+GAAA,kBAAkB,OAAA;AAAE,wGAAA,WAAW,OAAA;AAGhD,uCAAqE;AAA5D,yGAAA,aAAa,OAAA;AAAE,kGAAA,MAAM,OAAA;AAAE,4GAAA,gBAAgB,OAAA;AAEhD,6CAA4D;AAAnD,yGAAA,UAAU,OAAA;AAAE,8GAAA,eAAe,OAAA;AACpC,mCAAmC;AAA1B,iGAAA,OAAO,OAAA;AAChB,yCAWqB;AAVnB,gHAAA,mBAAmB,OAAA;AACnB,uHAAA,0BAA0B,OAAA;AAC1B,4GAAA,eAAe,OAAA;AACf,qHAAA,wBAAwB,OAAA;AACxB,oHAAA,uBAAuB,OAAA;AACvB,oHAAA,uBAAuB,OAAA;AACvB,kHAAA,qBAAqB,OAAA;AACrB,qHAAA,wBAAwB,OAAA;AACxB,mHAAA,sBAAsB,OAAA;AACtB,iHAAA,oBAAoB,OAAA"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,59 @@
1
+ import { type BurrowClient } from "./BurrowClient.js";
2
+ import { TlsMode } from "./Tls.js";
3
+ /** The SDK version, reported in the `Ratel-Client` header for the Burrow's audit trail. */
4
+ export declare const SDK_VERSION = "0.1.0";
5
+ /** The default `Ratel-Client` tag. It contains "sdk", so the Burrow classifies reads as the SDK channel. */
6
+ export declare const DEFAULT_CLIENT_TAG = "ratel-sdk-node/0.1.0";
7
+ /** Options common to both ways of opening a client. */
8
+ export interface BurrowOptions {
9
+ /** How to trust the Burrow's TLS certificate. Defaults to `TlsMode.Pinned` (trust-on-first-use). */
10
+ tls?: TlsMode;
11
+ /** The `Ratel-Client` tag recorded against each read. Defaults to {@link DEFAULT_CLIENT_TAG}. */
12
+ clientTag?: string;
13
+ }
14
+ /** Options for {@link Burrow.connect} — an explicit identity, no `~/.burrow` file involved. */
15
+ export interface ConnectOptions extends BurrowOptions {
16
+ burrowUrl: string;
17
+ machineId: string;
18
+ /** The PKCS#8 PEM of the machine's Ed25519 private key. */
19
+ privateKeyPem: string;
20
+ /**
21
+ * The directory a `TlsMode.Pinned` trust-on-first-use pin is written into, as `known_cert`.
22
+ *
23
+ * One directory per Burrow, so an app connecting to two of them must give each its own — a shared
24
+ * directory would have the second overwrite the first's pin. Omitted keeps the pin in memory for the life
25
+ * of the client and writes nothing, which is what a container with no writable state wants; pass
26
+ * `TlsMode.PinnedTo` there instead, so trust does not restart from scratch on every boot.
27
+ */
28
+ pinStoreDir?: string;
29
+ }
30
+ /**
31
+ * Entry point for reading secrets from a Burrow with a machine identity.
32
+ *
33
+ * A machine is bootstrapped into a Burrow from its Machines page (the `curl | sh` install), which writes an
34
+ * identity under `~/.burrow`. From then on:
35
+ *
36
+ * ```ts
37
+ * const burrow = Burrow(); // the one identity on this host
38
+ * const db = await burrow.getSecret("payments/prod/DATABASE");
39
+ * const [user, pass] = db.getFields("username", "password");
40
+ * ```
41
+ *
42
+ * Point it at a specific identity by slug / name / machine id / URL, or by path:
43
+ *
44
+ * ```ts
45
+ * Burrow("burrow-a1b2c3d4e5f60718"); // selector
46
+ * Burrow("/home/you/.burrow/burrows/burrow-a1b2c3d4e5f60718"); // path (dir or identity.json)
47
+ * ```
48
+ *
49
+ * For an app that ships its own key rather than the `~/.burrow` file, use {@link Burrow.connect}.
50
+ */
51
+ export interface BurrowFactory {
52
+ (target?: string | null, options?: BurrowOptions): BurrowClient;
53
+ /** The default `Ratel-Client` tag. It contains "sdk", so the Burrow classifies reads as the SDK channel. */
54
+ readonly DEFAULT_CLIENT_TAG: string;
55
+ /** Connect with an explicit identity rather than one discovered under `~/.burrow`. */
56
+ connect(options: ConnectOptions): BurrowClient;
57
+ }
58
+ export declare const Burrow: BurrowFactory;
59
+ //# sourceMappingURL=Burrow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Burrow.d.ts","sourceRoot":"","sources":["../../src/Burrow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AAE3E,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,2FAA2F;AAC3F,eAAO,MAAM,WAAW,UAAU,CAAC;AAEnC,4GAA4G;AAC5G,eAAO,MAAM,kBAAkB,yBAAkC,CAAC;AAElE,uDAAuD;AACvD,MAAM,WAAW,aAAa;IAC5B,oGAAoG;IACpG,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,iGAAiG;IACjG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,+FAA+F;AAC/F,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAoCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,aAAa;IAC5B,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;IAEhE,4GAA4G;IAC5G,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IAEpC,sFAAsF;IACtF,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,YAAY,CAAC;CAChD;AAED,eAAO,MAAM,MAAM,EAAE,aAAoE,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { DefaultBurrowClient } from "./BurrowClient.js";
2
+ import { Identities, parseEd25519PrivateKey } from "./Identity.js";
3
+ import { TlsMode } from "./Tls.js";
4
+ /** The SDK version, reported in the `Ratel-Client` header for the Burrow's audit trail. */
5
+ export const SDK_VERSION = "0.1.0";
6
+ /** The default `Ratel-Client` tag. It contains "sdk", so the Burrow classifies reads as the SDK channel. */
7
+ export const DEFAULT_CLIENT_TAG = `ratel-sdk-node/${SDK_VERSION}`;
8
+ /**
9
+ * Open a client. `target` is one of:
10
+ * - omitted — the single identity on this host (under `~/.burrow`, or `$BURROW_HOME`);
11
+ * - a selector — a burrow slug, name, machine id, or URL;
12
+ * - a filesystem path — a burrow identity directory or an `identity.json` file.
13
+ */
14
+ function open(target, options = {}) {
15
+ const id = Identities.select(target);
16
+ return new DefaultBurrowClient(id.burrowUrl, id.machineId, id.loadPrivateKey(), options.clientTag ?? DEFAULT_CLIENT_TAG, options.tls ?? TlsMode.Pinned, id.directory);
17
+ }
18
+ /**
19
+ * Connect with an explicit identity — for an app that manages its own key material rather than reading the
20
+ * `~/.burrow` file.
21
+ */
22
+ function connect(options) {
23
+ const key = parseEd25519PrivateKey(options.privateKeyPem, "provided PEM");
24
+ return new DefaultBurrowClient(options.burrowUrl, options.machineId, key, options.clientTag ?? DEFAULT_CLIENT_TAG, options.tls ?? TlsMode.Pinned, options.pinStoreDir ?? null);
25
+ }
26
+ export const Burrow = Object.assign(open, { DEFAULT_CLIENT_TAG, connect });
27
+ //# sourceMappingURL=Burrow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Burrow.js","sourceRoot":"","sources":["../../src/Burrow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,2FAA2F;AAC3F,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC;AAEnC,4GAA4G;AAC5G,MAAM,CAAC,MAAM,kBAAkB,GAAG,kBAAkB,WAAW,EAAE,CAAC;AA2BlE;;;;;GAKG;AACH,SAAS,IAAI,CAAC,MAAsB,EAAE,UAAyB,EAAE;IAC/D,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACrC,OAAO,IAAI,mBAAmB,CAC5B,EAAE,CAAC,SAAS,EACZ,EAAE,CAAC,SAAS,EACZ,EAAE,CAAC,cAAc,EAAE,EACnB,OAAO,CAAC,SAAS,IAAI,kBAAkB,EACvC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,MAAM,EAC7B,EAAE,CAAC,SAAS,CACb,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,OAAO,CAAC,OAAuB;IACtC,MAAM,GAAG,GAAG,sBAAsB,CAAC,OAAO,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IAC1E,OAAO,IAAI,mBAAmB,CAC5B,OAAO,CAAC,SAAS,EACjB,OAAO,CAAC,SAAS,EACjB,GAAG,EACH,OAAO,CAAC,SAAS,IAAI,kBAAkB,EACvC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,MAAM,EAC7B,OAAO,CAAC,WAAW,IAAI,IAAI,CAC5B,CAAC;AACJ,CAAC;AAiCD,MAAM,CAAC,MAAM,MAAM,GAAkB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,kBAAkB,EAAE,OAAO,EAAE,CAAC,CAAC"}