@forgezero/agent 0.1.39 → 0.1.40
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 +91 -18
- package/dist/agent-heartbeat.js +16 -14
- package/dist/agent-update-helper.js +15 -13
- package/dist/agent-update.js +15 -13
- package/dist/bootstrap.d.ts +13 -0
- package/dist/bootstrap.js +244 -832
- package/dist/cli/index.d.ts +0 -2
- package/dist/cloudflare-bootstrap.d.ts +21 -128
- package/dist/cloudflare-bootstrap.js +146 -788
- package/dist/cloudflare-edge.d.ts +3 -151
- package/dist/cloudflare-edge.js +28 -248
- package/dist/credential-schema.d.ts +60 -0
- package/dist/credential-schema.js +335 -0
- package/dist/definition.js +2 -2
- package/dist/deploy-file.js +2 -2
- package/dist/fz-agent.js +199 -47
- package/dist/fz.js +536 -1544
- package/dist/index.d.ts +33 -0
- package/dist/metal-bootstrap.js +1 -1
- package/dist/platform-bootstrap-runtime.d.ts +5 -0
- package/dist/platform-bootstrap-runtime.js +25 -6
- package/dist/project-context.js +1 -1
- package/dist/provision.js +18 -16
- package/dist/software-helper.js +2 -2
- package/dist/software.js +2 -2
- package/dist/version.d.ts +1 -1
- package/package.json +8 -4
- package/dist/cli/custody.d.ts +0 -35
package/dist/fz.js
CHANGED
|
@@ -1766,7 +1766,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
|
|
|
1766
1766
|
const msg = concatBytes2(...msgs);
|
|
1767
1767
|
return modN_LE(hash(domain(msg, abytes2(context, undefined, "context"), !!prehash)));
|
|
1768
1768
|
}
|
|
1769
|
-
function
|
|
1769
|
+
function sign(msg, secretKey, options = {}) {
|
|
1770
1770
|
validateObject(options, {}, {}, "options");
|
|
1771
1771
|
msg = abytes2(msg, undefined, "message");
|
|
1772
1772
|
if (prehash)
|
|
@@ -1855,7 +1855,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
|
|
|
1855
1855
|
return Object.freeze({
|
|
1856
1856
|
keygen: createKeygen(randomSecretKey, getPublicKey),
|
|
1857
1857
|
getPublicKey,
|
|
1858
|
-
sign
|
|
1858
|
+
sign,
|
|
1859
1859
|
verify,
|
|
1860
1860
|
utils,
|
|
1861
1861
|
Point,
|
|
@@ -1875,168 +1875,14 @@ var init_edwards = __esm(() => {
|
|
|
1875
1875
|
_8n2 = /* @__PURE__ */ BigInt(8);
|
|
1876
1876
|
});
|
|
1877
1877
|
|
|
1878
|
-
// ../node_modules/.bun/@noble+curves@2.3.0/node_modules/@noble/curves/abstract/montgomery.js
|
|
1879
|
-
function cmask(P, swap) {
|
|
1880
|
-
return P + swap - (swap >> _1n5 << _1n5);
|
|
1881
|
-
}
|
|
1882
|
-
function cswap(P) {
|
|
1883
|
-
const offset = BigInt(6) * P;
|
|
1884
|
-
return (mask, x_2, x_3) => {
|
|
1885
|
-
const sum = x_2 + x_3;
|
|
1886
|
-
const d = offset + x_3 - x_2;
|
|
1887
|
-
const a = (d * mask + x_2) % P;
|
|
1888
|
-
return { x_2: a, x_3: sum - a };
|
|
1889
|
-
};
|
|
1890
|
-
}
|
|
1891
|
-
function validateOpts(curve) {
|
|
1892
|
-
validateObject(curve, {
|
|
1893
|
-
P: "bigint",
|
|
1894
|
-
type: "string",
|
|
1895
|
-
adjustScalarBytes: "function",
|
|
1896
|
-
powPminus2: "function"
|
|
1897
|
-
}, {
|
|
1898
|
-
randomBytes: "function",
|
|
1899
|
-
scalarMultBase: "function"
|
|
1900
|
-
});
|
|
1901
|
-
return Object.freeze({ ...curve });
|
|
1902
|
-
}
|
|
1903
|
-
function montgomery(curveDef) {
|
|
1904
|
-
const CURVE = validateOpts(curveDef);
|
|
1905
|
-
const { P, type, adjustScalarBytes, powPminus2, randomBytes: rand } = CURVE;
|
|
1906
|
-
const mulBaseHook = CURVE.scalarMultBase;
|
|
1907
|
-
const is25519 = type === "x25519";
|
|
1908
|
-
if (!is25519 && type !== "x448")
|
|
1909
|
-
throw new Error("invalid type");
|
|
1910
|
-
const randomBytes_ = rand === undefined ? randomBytes2 : rand;
|
|
1911
|
-
const montgomeryBits = is25519 ? 255 : 448;
|
|
1912
|
-
const swap = cswap(P);
|
|
1913
|
-
const fieldLen = is25519 ? 32 : 56;
|
|
1914
|
-
const Gu = is25519 ? BigInt(9) : BigInt(5);
|
|
1915
|
-
const a24 = is25519 ? BigInt(121665) : BigInt(39081);
|
|
1916
|
-
const minScalar = is25519 ? _2n3 ** BigInt(254) : _2n3 ** BigInt(447);
|
|
1917
|
-
const maxAdded = is25519 ? BigInt(8) * (_2n3 ** BigInt(251) - _1n5) : BigInt(4) * (_2n3 ** BigInt(445) - _1n5);
|
|
1918
|
-
const maxScalar = minScalar + maxAdded + _1n5;
|
|
1919
|
-
const modP = (n) => mod(n, P);
|
|
1920
|
-
const GuBytes = encodeU(Gu);
|
|
1921
|
-
function encodeU(u) {
|
|
1922
|
-
return numberToBytesLE(modP(u), fieldLen);
|
|
1923
|
-
}
|
|
1924
|
-
function decodeU(u) {
|
|
1925
|
-
const _u = copyBytes(abytes2(u, fieldLen, "uCoordinate"));
|
|
1926
|
-
if (is25519)
|
|
1927
|
-
_u[31] &= 127;
|
|
1928
|
-
return modP(bytesToNumberLE(_u));
|
|
1929
|
-
}
|
|
1930
|
-
function decodeScalar(scalar) {
|
|
1931
|
-
return bytesToNumberLE(adjustScalarBytes(copyBytes(abytes2(scalar, fieldLen, "scalar"))));
|
|
1932
|
-
}
|
|
1933
|
-
const lowOrderU = new Set(is25519 ? [
|
|
1934
|
-
_0n5,
|
|
1935
|
-
_1n5,
|
|
1936
|
-
P - _1n5,
|
|
1937
|
-
BigInt("325606250916557431795983626356110631294008115727848805560023387167927233504"),
|
|
1938
|
-
BigInt("39382357235489614581723060781553021112529911719440698176882885853963445705823")
|
|
1939
|
-
] : [_0n5, _1n5, P - _1n5]);
|
|
1940
|
-
function scalarMult(scalar, u) {
|
|
1941
|
-
const pointU = decodeU(u);
|
|
1942
|
-
if (lowOrderU.has(pointU))
|
|
1943
|
-
throw new Error("invalid private or public key received");
|
|
1944
|
-
const pu = montgomeryLadder(pointU, decodeScalar(scalar));
|
|
1945
|
-
if (pu === _0n5)
|
|
1946
|
-
throw new Error("invalid private or public key received");
|
|
1947
|
-
return encodeU(pu);
|
|
1948
|
-
}
|
|
1949
|
-
function scalarMultBase(scalar) {
|
|
1950
|
-
if (mulBaseHook === undefined)
|
|
1951
|
-
return scalarMult(scalar, GuBytes);
|
|
1952
|
-
const k = decodeScalar(scalar);
|
|
1953
|
-
aInRange("scalar", k, minScalar, maxScalar);
|
|
1954
|
-
const pu = modP(mulBaseHook(k));
|
|
1955
|
-
if (pu === _0n5)
|
|
1956
|
-
throw new Error("invalid private or public key received");
|
|
1957
|
-
return encodeU(pu);
|
|
1958
|
-
}
|
|
1959
|
-
const getPublicKey = scalarMultBase;
|
|
1960
|
-
const getSharedSecret = scalarMult;
|
|
1961
|
-
function montgomeryLadder(u, scalar) {
|
|
1962
|
-
aInRange("u", u, _0n5, P);
|
|
1963
|
-
aInRange("scalar", scalar, minScalar, maxScalar);
|
|
1964
|
-
const k = scalar;
|
|
1965
|
-
const x_1 = u;
|
|
1966
|
-
let x_2 = _1n5;
|
|
1967
|
-
let z_2 = _0n5;
|
|
1968
|
-
let x_3 = u;
|
|
1969
|
-
let z_3 = _1n5;
|
|
1970
|
-
const kx = k ^ k >> _1n5;
|
|
1971
|
-
for (let t = BigInt(montgomeryBits - 1);t >= _0n5; t--) {
|
|
1972
|
-
const mask2 = cmask(P, kx >> t);
|
|
1973
|
-
({ x_2, x_3 } = swap(mask2, x_2, x_3));
|
|
1974
|
-
({ x_2: z_2, x_3: z_3 } = swap(mask2, z_2, z_3));
|
|
1975
|
-
const A = x_2 + z_2;
|
|
1976
|
-
const AA = modP(A * A);
|
|
1977
|
-
const B = x_2 - z_2;
|
|
1978
|
-
const BB = modP(B * B);
|
|
1979
|
-
const E = AA - BB;
|
|
1980
|
-
const C = x_3 + z_3;
|
|
1981
|
-
const D = x_3 - z_3;
|
|
1982
|
-
const DA = modP(D * A);
|
|
1983
|
-
const CB = modP(C * B);
|
|
1984
|
-
const dacb = DA + CB;
|
|
1985
|
-
const da_cb = DA - CB;
|
|
1986
|
-
x_3 = modP(dacb * dacb);
|
|
1987
|
-
z_3 = modP(x_1 * modP(da_cb * da_cb));
|
|
1988
|
-
x_2 = modP(AA * BB);
|
|
1989
|
-
z_2 = modP(E * (AA + modP(a24 * E)));
|
|
1990
|
-
}
|
|
1991
|
-
const mask = cmask(P, k);
|
|
1992
|
-
({ x_2, x_3 } = swap(mask, x_2, x_3));
|
|
1993
|
-
({ x_2: z_2, x_3: z_3 } = swap(mask, z_2, z_3));
|
|
1994
|
-
const z2 = powPminus2(z_2);
|
|
1995
|
-
return modP(x_2 * z2);
|
|
1996
|
-
}
|
|
1997
|
-
const lengths = {
|
|
1998
|
-
secretKey: fieldLen,
|
|
1999
|
-
publicKey: fieldLen,
|
|
2000
|
-
seed: fieldLen
|
|
2001
|
-
};
|
|
2002
|
-
const randomSecretKey = (seed) => {
|
|
2003
|
-
seed = seed === undefined ? randomBytes_(fieldLen) : seed;
|
|
2004
|
-
abytes2(seed, lengths.seed, "seed");
|
|
2005
|
-
return seed;
|
|
2006
|
-
};
|
|
2007
|
-
const utils = { randomSecretKey };
|
|
2008
|
-
Object.freeze(lengths);
|
|
2009
|
-
Object.freeze(utils);
|
|
2010
|
-
return Object.freeze({
|
|
2011
|
-
keygen: createKeygen(randomSecretKey, getPublicKey),
|
|
2012
|
-
getSharedSecret,
|
|
2013
|
-
getPublicKey,
|
|
2014
|
-
scalarMult,
|
|
2015
|
-
scalarMultBase,
|
|
2016
|
-
utils,
|
|
2017
|
-
GuBytes: GuBytes.slice(),
|
|
2018
|
-
lengths
|
|
2019
|
-
});
|
|
2020
|
-
}
|
|
2021
|
-
var _0n5, _1n5, _2n3;
|
|
2022
|
-
var init_montgomery = __esm(() => {
|
|
2023
|
-
init_utils2();
|
|
2024
|
-
init_curve();
|
|
2025
|
-
init_modular();
|
|
2026
|
-
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
2027
|
-
_0n5 = /* @__PURE__ */ BigInt(0);
|
|
2028
|
-
_1n5 = /* @__PURE__ */ BigInt(1);
|
|
2029
|
-
_2n3 = /* @__PURE__ */ BigInt(2);
|
|
2030
|
-
});
|
|
2031
|
-
|
|
2032
1878
|
// ../node_modules/.bun/@noble+curves@2.3.0/node_modules/@noble/curves/ed25519.js
|
|
2033
1879
|
function ed25519_pow_2_252_3(x) {
|
|
2034
1880
|
const _10n = BigInt(10), _20n = BigInt(20), _40n = BigInt(40), _80n = BigInt(80);
|
|
2035
1881
|
const P = ed25519_CURVE_p;
|
|
2036
1882
|
const x2 = x * x % P;
|
|
2037
1883
|
const b2 = x2 * x % P;
|
|
2038
|
-
const b4 = pow2(b2,
|
|
2039
|
-
const b5 = pow2(b4,
|
|
1884
|
+
const b4 = pow2(b2, _2n3, P) * b2 % P;
|
|
1885
|
+
const b5 = pow2(b4, _1n5, P) * x % P;
|
|
2040
1886
|
const b10 = pow2(b5, _5n2, P) * b5 % P;
|
|
2041
1887
|
const b20 = pow2(b10, _10n, P) * b10 % P;
|
|
2042
1888
|
const b40 = pow2(b20, _20n, P) * b20 % P;
|
|
@@ -2044,7 +1890,7 @@ function ed25519_pow_2_252_3(x) {
|
|
|
2044
1890
|
const b160 = pow2(b80, _80n, P) * b80 % P;
|
|
2045
1891
|
const b240 = pow2(b160, _80n, P) * b80 % P;
|
|
2046
1892
|
const b250 = pow2(b240, _10n, P) * b10 % P;
|
|
2047
|
-
const pow_p_5_8 = pow2(b250,
|
|
1893
|
+
const pow_p_5_8 = pow2(b250, _2n3, P) * x % P;
|
|
2048
1894
|
return { pow_p_5_8, b2 };
|
|
2049
1895
|
}
|
|
2050
1896
|
function adjustScalarBytes(bytes) {
|
|
@@ -2075,7 +1921,7 @@ function uvRatio(u, v) {
|
|
|
2075
1921
|
}
|
|
2076
1922
|
function toMontgomery(point) {
|
|
2077
1923
|
const { y } = point;
|
|
2078
|
-
return Fp.toBytes(Fp.div(
|
|
1924
|
+
return Fp.toBytes(Fp.div(_1n5 + y, _1n5 - y));
|
|
2079
1925
|
}
|
|
2080
1926
|
function toMontgomerySecret(secretKey) {
|
|
2081
1927
|
const size = ed25519_Point.Fp.BYTES;
|
|
@@ -2085,18 +1931,15 @@ function toMontgomerySecret(secretKey) {
|
|
|
2085
1931
|
function ed(opts) {
|
|
2086
1932
|
return eddsa(ed25519_Point, sha512, Object.assign({ adjustScalarBytes, toMontgomery, toMontgomerySecret, zip215: true }, opts));
|
|
2087
1933
|
}
|
|
2088
|
-
var
|
|
1934
|
+
var _1n5, _2n3, _5n2, _8n3, ed25519_CURVE_p, ed25519_CURVE, ED25519_SQRT_M1, ed25519_Point, Fp, ed25519;
|
|
2089
1935
|
var init_ed25519 = __esm(() => {
|
|
2090
1936
|
init_sha2();
|
|
2091
1937
|
init_utils();
|
|
2092
1938
|
init_edwards();
|
|
2093
1939
|
init_modular();
|
|
2094
|
-
init_montgomery();
|
|
2095
1940
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
_2n4 = /* @__PURE__ */ BigInt(2);
|
|
2099
|
-
_3n2 = /* @__PURE__ */ BigInt(3);
|
|
1941
|
+
_1n5 = /* @__PURE__ */ BigInt(1);
|
|
1942
|
+
_2n3 = /* @__PURE__ */ BigInt(2);
|
|
2100
1943
|
_5n2 = /* @__PURE__ */ BigInt(5);
|
|
2101
1944
|
_8n3 = /* @__PURE__ */ BigInt(8);
|
|
2102
1945
|
ed25519_CURVE_p = /* @__PURE__ */ BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed");
|
|
@@ -2113,26 +1956,6 @@ var init_ed25519 = __esm(() => {
|
|
|
2113
1956
|
ed25519_Point = /* @__PURE__ */ edwards(ed25519_CURVE, { uvRatio });
|
|
2114
1957
|
Fp = /* @__PURE__ */ (() => ed25519_Point.Fp)();
|
|
2115
1958
|
ed25519 = /* @__PURE__ */ ed({});
|
|
2116
|
-
x25519 = /* @__PURE__ */ (() => {
|
|
2117
|
-
const P = ed25519_CURVE_p;
|
|
2118
|
-
const powPminus2 = (x) => {
|
|
2119
|
-
const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x);
|
|
2120
|
-
return mod(pow2(pow_p_5_8, _3n2, P) * b2, P);
|
|
2121
|
-
};
|
|
2122
|
-
return montgomery({
|
|
2123
|
-
P,
|
|
2124
|
-
type: "x25519",
|
|
2125
|
-
powPminus2,
|
|
2126
|
-
adjustScalarBytes,
|
|
2127
|
-
scalarMultBase: (k) => {
|
|
2128
|
-
const kn = mod(k, ed25519_Point.Fn.ORDER);
|
|
2129
|
-
if (kn === _0n6)
|
|
2130
|
-
return _0n6;
|
|
2131
|
-
const p = ed25519_Point.BASE.multiply(kn);
|
|
2132
|
-
return mod((p.Z + p.Y) * powPminus2(mod(p.Z - p.Y, P)), P);
|
|
2133
|
-
}
|
|
2134
|
-
});
|
|
2135
|
-
})();
|
|
2136
1959
|
});
|
|
2137
1960
|
|
|
2138
1961
|
// ../node_modules/.bun/@noble+hashes@2.2.0/node_modules/@noble/hashes/utils.js
|
|
@@ -2347,7 +2170,7 @@ function asafenumber2(value, title = "") {
|
|
|
2347
2170
|
function hexToNumber2(hex) {
|
|
2348
2171
|
if (typeof hex !== "string")
|
|
2349
2172
|
throw new TypeError("hex string expected, got " + typeof hex);
|
|
2350
|
-
return hex === "" ?
|
|
2173
|
+
return hex === "" ? _0n5 : BigInt("0x" + hex);
|
|
2351
2174
|
}
|
|
2352
2175
|
function bytesToNumberBE2(bytes) {
|
|
2353
2176
|
return hexToNumber2(bytesToHex3(bytes));
|
|
@@ -2400,10 +2223,10 @@ function aInRange2(title, n, min, max) {
|
|
|
2400
2223
|
throw new RangeError("expected valid " + title + ": " + min + " <= n < " + max + ", got " + n);
|
|
2401
2224
|
}
|
|
2402
2225
|
function bitLen2(n) {
|
|
2403
|
-
if (n <
|
|
2226
|
+
if (n < _0n5)
|
|
2404
2227
|
throw new Error("expected non-negative bigint, got " + n);
|
|
2405
2228
|
let len;
|
|
2406
|
-
for (len = 0;n >
|
|
2229
|
+
for (len = 0;n > _0n5; n >>= _1n6, len += 1)
|
|
2407
2230
|
;
|
|
2408
2231
|
return len;
|
|
2409
2232
|
}
|
|
@@ -2424,7 +2247,7 @@ function validateObject2(object, fields = {}, optFields = {}) {
|
|
|
2424
2247
|
iter(fields, false);
|
|
2425
2248
|
iter(optFields, true);
|
|
2426
2249
|
}
|
|
2427
|
-
var abytes4 = (value, length, title) => abytes3(value, length, title), anumber4, bytesToHex4, concatBytes4 = (...arrays) => concatBytes3(...arrays), hexToBytes4 = (hex) => hexToBytes3(hex), isBytes4, randomBytes4 = (bytesLength) => randomBytes3(bytesLength),
|
|
2250
|
+
var abytes4 = (value, length, title) => abytes3(value, length, title), anumber4, bytesToHex4, concatBytes4 = (...arrays) => concatBytes3(...arrays), hexToBytes4 = (hex) => hexToBytes3(hex), isBytes4, randomBytes4 = (bytesLength) => randomBytes3(bytesLength), _0n5, _1n6, isPosBig2 = (n) => typeof n === "bigint" && _0n5 <= n, bitMask2 = (n) => (_1n6 << BigInt(n)) - _1n6, notImplemented2 = () => {
|
|
2428
2251
|
throw new Error("not implemented");
|
|
2429
2252
|
};
|
|
2430
2253
|
var init_utils4 = __esm(() => {
|
|
@@ -2433,8 +2256,8 @@ var init_utils4 = __esm(() => {
|
|
|
2433
2256
|
anumber4 = anumber3;
|
|
2434
2257
|
bytesToHex4 = bytesToHex3;
|
|
2435
2258
|
isBytes4 = isBytes3;
|
|
2436
|
-
|
|
2437
|
-
|
|
2259
|
+
_0n5 = /* @__PURE__ */ BigInt(0);
|
|
2260
|
+
_1n6 = /* @__PURE__ */ BigInt(1);
|
|
2438
2261
|
});
|
|
2439
2262
|
|
|
2440
2263
|
// ../node_modules/.bun/@noble+hashes@2.2.0/node_modules/@noble/hashes/_u64.js
|
|
@@ -2634,28 +2457,28 @@ class Keccak {
|
|
|
2634
2457
|
return to;
|
|
2635
2458
|
}
|
|
2636
2459
|
}
|
|
2637
|
-
var
|
|
2460
|
+
var _0n6, _1n7, _2n4, _7n2, _256n, _0x71n, SHA3_PI, SHA3_ROTL, _SHA3_IOTA, IOTAS, SHA3_IOTA_H, SHA3_IOTA_L, rotlH = (h, l, s) => s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s), rotlL = (h, l, s) => s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s), genKeccak = (suffix, blockLen, outputLen, info = {}) => createHasher2(() => new Keccak(blockLen, suffix, outputLen), info), sha3_256, sha3_512, genShake = (suffix, blockLen, outputLen, info = {}) => createHasher2((opts = {}) => new Keccak(blockLen, suffix, opts.dkLen === undefined ? outputLen : opts.dkLen, true), info), shake128, shake256;
|
|
2638
2461
|
var init_sha3 = __esm(() => {
|
|
2639
2462
|
init__u642();
|
|
2640
2463
|
init_utils3();
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2464
|
+
_0n6 = BigInt(0);
|
|
2465
|
+
_1n7 = BigInt(1);
|
|
2466
|
+
_2n4 = BigInt(2);
|
|
2644
2467
|
_7n2 = BigInt(7);
|
|
2645
2468
|
_256n = BigInt(256);
|
|
2646
2469
|
_0x71n = BigInt(113);
|
|
2647
2470
|
SHA3_PI = [];
|
|
2648
2471
|
SHA3_ROTL = [];
|
|
2649
2472
|
_SHA3_IOTA = [];
|
|
2650
|
-
for (let round = 0, R =
|
|
2473
|
+
for (let round = 0, R = _1n7, x = 1, y = 0;round < 24; round++) {
|
|
2651
2474
|
[x, y] = [y, (2 * x + 3 * y) % 5];
|
|
2652
2475
|
SHA3_PI.push(2 * (5 * y + x));
|
|
2653
2476
|
SHA3_ROTL.push((round + 1) * (round + 2) / 2 % 64);
|
|
2654
|
-
let t =
|
|
2477
|
+
let t = _0n6;
|
|
2655
2478
|
for (let j = 0;j < 7; j++) {
|
|
2656
|
-
R = (R <<
|
|
2657
|
-
if (R &
|
|
2658
|
-
t ^=
|
|
2479
|
+
R = (R << _1n7 ^ (R >> _7n2) * _0x71n) % _256n;
|
|
2480
|
+
if (R & _2n4)
|
|
2481
|
+
t ^= _1n7 << (_1n7 << BigInt(j)) - _1n7;
|
|
2659
2482
|
}
|
|
2660
2483
|
_SHA3_IOTA.push(t);
|
|
2661
2484
|
}
|
|
@@ -2765,12 +2588,12 @@ function equalBytes2(a, b) {
|
|
|
2765
2588
|
function copyBytes3(bytes) {
|
|
2766
2589
|
return Uint8Array.from(abytes3(bytes));
|
|
2767
2590
|
}
|
|
2768
|
-
function
|
|
2591
|
+
function validateOpts(opts) {
|
|
2769
2592
|
if (Object.prototype.toString.call(opts) !== "[object Object]")
|
|
2770
2593
|
throw new TypeError("expected valid options object");
|
|
2771
2594
|
}
|
|
2772
2595
|
function validateVerOpts(opts) {
|
|
2773
|
-
|
|
2596
|
+
validateOpts(opts);
|
|
2774
2597
|
if (opts.context !== undefined)
|
|
2775
2598
|
abytes3(opts.context, undefined, "opts.context");
|
|
2776
2599
|
}
|
|
@@ -3011,7 +2834,7 @@ var init__crystals = __esm(() => {
|
|
|
3011
2834
|
|
|
3012
2835
|
// ../node_modules/.bun/@noble+post-quantum@0.6.1/node_modules/@noble/post-quantum/ml-dsa.js
|
|
3013
2836
|
function validateInternalOpts(opts) {
|
|
3014
|
-
|
|
2837
|
+
validateOpts(opts);
|
|
3015
2838
|
if (opts.externalMu !== undefined)
|
|
3016
2839
|
abool2(opts.externalMu, "opts.externalMu");
|
|
3017
2840
|
}
|
|
@@ -3520,30 +3343,30 @@ var init_ml_dsa = __esm(() => {
|
|
|
3520
3343
|
|
|
3521
3344
|
// ../node_modules/.bun/@noble+curves@2.2.0/node_modules/@noble/curves/abstract/modular.js
|
|
3522
3345
|
function mod2(a, b) {
|
|
3523
|
-
if (b <=
|
|
3346
|
+
if (b <= _0n7)
|
|
3524
3347
|
throw new Error("mod: expected positive modulus, got " + b);
|
|
3525
3348
|
const result = a % b;
|
|
3526
|
-
return result >=
|
|
3349
|
+
return result >= _0n7 ? result : b + result;
|
|
3527
3350
|
}
|
|
3528
3351
|
function pow22(x, power, modulo) {
|
|
3529
|
-
if (power <
|
|
3352
|
+
if (power < _0n7)
|
|
3530
3353
|
throw new Error("pow2: expected non-negative exponent, got " + power);
|
|
3531
3354
|
let res = x;
|
|
3532
|
-
while (power-- >
|
|
3355
|
+
while (power-- > _0n7) {
|
|
3533
3356
|
res *= res;
|
|
3534
3357
|
res %= modulo;
|
|
3535
3358
|
}
|
|
3536
3359
|
return res;
|
|
3537
3360
|
}
|
|
3538
3361
|
function invert2(number, modulo) {
|
|
3539
|
-
if (number ===
|
|
3362
|
+
if (number === _0n7)
|
|
3540
3363
|
throw new Error("invert: expected non-zero number");
|
|
3541
|
-
if (modulo <=
|
|
3364
|
+
if (modulo <= _0n7)
|
|
3542
3365
|
throw new Error("invert: expected positive modulus, got " + modulo);
|
|
3543
3366
|
let a = mod2(number, modulo);
|
|
3544
3367
|
let b = modulo;
|
|
3545
|
-
let x =
|
|
3546
|
-
while (a !==
|
|
3368
|
+
let x = _0n7, y = _1n8, u = _1n8, v = _0n7;
|
|
3369
|
+
while (a !== _0n7) {
|
|
3547
3370
|
const q = b / a;
|
|
3548
3371
|
const r = b - a * q;
|
|
3549
3372
|
const m = x - u * q;
|
|
@@ -3551,7 +3374,7 @@ function invert2(number, modulo) {
|
|
|
3551
3374
|
b = a, a = r, x = u, y = v, u = m, v = n;
|
|
3552
3375
|
}
|
|
3553
3376
|
const gcd = b;
|
|
3554
|
-
if (gcd !==
|
|
3377
|
+
if (gcd !== _1n8)
|
|
3555
3378
|
throw new Error("invert: does not exist");
|
|
3556
3379
|
return mod2(x, modulo);
|
|
3557
3380
|
}
|
|
@@ -3562,7 +3385,7 @@ function assertIsSquare2(Fp2, root, n) {
|
|
|
3562
3385
|
}
|
|
3563
3386
|
function sqrt3mod42(Fp2, n) {
|
|
3564
3387
|
const F2 = Fp2;
|
|
3565
|
-
const p1div4 = (F2.ORDER +
|
|
3388
|
+
const p1div4 = (F2.ORDER + _1n8) / _4n4;
|
|
3566
3389
|
const root = F2.pow(n, p1div4);
|
|
3567
3390
|
assertIsSquare2(F2, root, n);
|
|
3568
3391
|
return root;
|
|
@@ -3570,10 +3393,10 @@ function sqrt3mod42(Fp2, n) {
|
|
|
3570
3393
|
function sqrt5mod82(Fp2, n) {
|
|
3571
3394
|
const F2 = Fp2;
|
|
3572
3395
|
const p5div8 = (F2.ORDER - _5n3) / _8n4;
|
|
3573
|
-
const n2 = F2.mul(n,
|
|
3396
|
+
const n2 = F2.mul(n, _2n5);
|
|
3574
3397
|
const v = F2.pow(n2, p5div8);
|
|
3575
3398
|
const nv = F2.mul(n, v);
|
|
3576
|
-
const i = F2.mul(F2.mul(nv,
|
|
3399
|
+
const i = F2.mul(F2.mul(nv, _2n5), v);
|
|
3577
3400
|
const root = F2.mul(nv, F2.sub(i, F2.ONE));
|
|
3578
3401
|
assertIsSquare2(F2, root, n);
|
|
3579
3402
|
return root;
|
|
@@ -3602,15 +3425,15 @@ function sqrt9mod162(P) {
|
|
|
3602
3425
|
};
|
|
3603
3426
|
}
|
|
3604
3427
|
function tonelliShanks2(P) {
|
|
3605
|
-
if (P <
|
|
3428
|
+
if (P < _3n2)
|
|
3606
3429
|
throw new Error("sqrt is not defined for small field");
|
|
3607
|
-
let Q2 = P -
|
|
3430
|
+
let Q2 = P - _1n8;
|
|
3608
3431
|
let S = 0;
|
|
3609
|
-
while (Q2 %
|
|
3610
|
-
Q2 /=
|
|
3432
|
+
while (Q2 % _2n5 === _0n7) {
|
|
3433
|
+
Q2 /= _2n5;
|
|
3611
3434
|
S++;
|
|
3612
3435
|
}
|
|
3613
|
-
let Z =
|
|
3436
|
+
let Z = _2n5;
|
|
3614
3437
|
const _Fp = Field2(P);
|
|
3615
3438
|
while (FpLegendre2(_Fp, Z) === 1) {
|
|
3616
3439
|
if (Z++ > 1000)
|
|
@@ -3619,7 +3442,7 @@ function tonelliShanks2(P) {
|
|
|
3619
3442
|
if (S === 1)
|
|
3620
3443
|
return sqrt3mod42;
|
|
3621
3444
|
let cc = _Fp.pow(Z, Q2);
|
|
3622
|
-
const Q1div2 = (Q2 +
|
|
3445
|
+
const Q1div2 = (Q2 + _1n8) / _2n5;
|
|
3623
3446
|
return function tonelliSlow(Fp2, n) {
|
|
3624
3447
|
const F2 = Fp2;
|
|
3625
3448
|
if (F2.is0(n))
|
|
@@ -3641,7 +3464,7 @@ function tonelliShanks2(P) {
|
|
|
3641
3464
|
if (i === M)
|
|
3642
3465
|
throw new Error("Cannot find square root");
|
|
3643
3466
|
}
|
|
3644
|
-
const exponent =
|
|
3467
|
+
const exponent = _1n8 << BigInt(M - i - 1);
|
|
3645
3468
|
const b = F2.pow(c, exponent);
|
|
3646
3469
|
M = i;
|
|
3647
3470
|
c = F2.sqr(b);
|
|
@@ -3652,7 +3475,7 @@ function tonelliShanks2(P) {
|
|
|
3652
3475
|
};
|
|
3653
3476
|
}
|
|
3654
3477
|
function FpSqrt2(P) {
|
|
3655
|
-
if (P % _4n4 ===
|
|
3478
|
+
if (P % _4n4 === _3n2)
|
|
3656
3479
|
return sqrt3mod42;
|
|
3657
3480
|
if (P % _8n4 === _5n3)
|
|
3658
3481
|
return sqrt5mod82;
|
|
@@ -3675,25 +3498,25 @@ function validateField2(field) {
|
|
|
3675
3498
|
asafenumber2(field.BITS, "BITS");
|
|
3676
3499
|
if (field.BYTES < 1 || field.BITS < 1)
|
|
3677
3500
|
throw new Error("invalid field: expected BYTES/BITS > 0");
|
|
3678
|
-
if (field.ORDER <=
|
|
3501
|
+
if (field.ORDER <= _1n8)
|
|
3679
3502
|
throw new Error("invalid field: expected ORDER > 1, got " + field.ORDER);
|
|
3680
3503
|
return field;
|
|
3681
3504
|
}
|
|
3682
3505
|
function FpPow(Fp2, num, power) {
|
|
3683
3506
|
const F2 = Fp2;
|
|
3684
|
-
if (power <
|
|
3507
|
+
if (power < _0n7)
|
|
3685
3508
|
throw new Error("invalid exponent, negatives unsupported");
|
|
3686
|
-
if (power ===
|
|
3509
|
+
if (power === _0n7)
|
|
3687
3510
|
return F2.ONE;
|
|
3688
|
-
if (power ===
|
|
3511
|
+
if (power === _1n8)
|
|
3689
3512
|
return num;
|
|
3690
3513
|
let p = F2.ONE;
|
|
3691
3514
|
let d = num;
|
|
3692
|
-
while (power >
|
|
3693
|
-
if (power &
|
|
3515
|
+
while (power > _0n7) {
|
|
3516
|
+
if (power & _1n8)
|
|
3694
3517
|
p = F2.mul(p, d);
|
|
3695
3518
|
d = F2.sqr(d);
|
|
3696
|
-
power >>=
|
|
3519
|
+
power >>= _1n8;
|
|
3697
3520
|
}
|
|
3698
3521
|
return p;
|
|
3699
3522
|
}
|
|
@@ -3717,7 +3540,7 @@ function FpInvertBatch2(Fp2, nums, passZero = false) {
|
|
|
3717
3540
|
}
|
|
3718
3541
|
function FpLegendre2(Fp2, n) {
|
|
3719
3542
|
const F2 = Fp2;
|
|
3720
|
-
const p1mod2 = (F2.ORDER -
|
|
3543
|
+
const p1mod2 = (F2.ORDER - _1n8) / _2n5;
|
|
3721
3544
|
const powered = F2.pow(n, p1mod2);
|
|
3722
3545
|
const yes = F2.eql(powered, F2.ONE);
|
|
3723
3546
|
const zero = F2.eql(powered, F2.ZERO);
|
|
@@ -3729,7 +3552,7 @@ function FpLegendre2(Fp2, n) {
|
|
|
3729
3552
|
function nLength2(n, nBitLength) {
|
|
3730
3553
|
if (nBitLength !== undefined)
|
|
3731
3554
|
anumber4(nBitLength);
|
|
3732
|
-
if (n <=
|
|
3555
|
+
if (n <= _0n7)
|
|
3733
3556
|
throw new Error("invalid n length: expected positive n, got " + n);
|
|
3734
3557
|
if (nBitLength !== undefined && nBitLength < 1)
|
|
3735
3558
|
throw new Error("invalid n length: expected positive bit length, got " + nBitLength);
|
|
@@ -3746,12 +3569,12 @@ class _Field2 {
|
|
|
3746
3569
|
BITS;
|
|
3747
3570
|
BYTES;
|
|
3748
3571
|
isLE;
|
|
3749
|
-
ZERO =
|
|
3750
|
-
ONE =
|
|
3572
|
+
ZERO = _0n7;
|
|
3573
|
+
ONE = _1n8;
|
|
3751
3574
|
_lengths;
|
|
3752
3575
|
_mod;
|
|
3753
3576
|
constructor(ORDER, opts = {}) {
|
|
3754
|
-
if (ORDER <=
|
|
3577
|
+
if (ORDER <= _1n8)
|
|
3755
3578
|
throw new Error("invalid field: expected ORDER > 1, got " + ORDER);
|
|
3756
3579
|
let _nbitLength = undefined;
|
|
3757
3580
|
this.isLE = false;
|
|
@@ -3781,16 +3604,16 @@ class _Field2 {
|
|
|
3781
3604
|
isValid(num) {
|
|
3782
3605
|
if (typeof num !== "bigint")
|
|
3783
3606
|
throw new TypeError("invalid field element: expected bigint, got " + typeof num);
|
|
3784
|
-
return
|
|
3607
|
+
return _0n7 <= num && num < this.ORDER;
|
|
3785
3608
|
}
|
|
3786
3609
|
is0(num) {
|
|
3787
|
-
return num ===
|
|
3610
|
+
return num === _0n7;
|
|
3788
3611
|
}
|
|
3789
3612
|
isValidNot0(num) {
|
|
3790
3613
|
return !this.is0(num) && this.isValid(num);
|
|
3791
3614
|
}
|
|
3792
3615
|
isOdd(num) {
|
|
3793
|
-
return (num &
|
|
3616
|
+
return (num & _1n8) === _1n8;
|
|
3794
3617
|
}
|
|
3795
3618
|
neg(num) {
|
|
3796
3619
|
return mod2(-num, this.ORDER);
|
|
@@ -3873,14 +3696,14 @@ class _Field2 {
|
|
|
3873
3696
|
function Field2(ORDER, opts = {}) {
|
|
3874
3697
|
return new _Field2(ORDER, opts);
|
|
3875
3698
|
}
|
|
3876
|
-
var
|
|
3699
|
+
var _0n7, _1n8, _2n5, _3n2, _4n4, _5n3, _7n3, _8n4, _9n2, _16n2, isNegativeLE2 = (num, modulo) => (mod2(num, modulo) & _1n8) === _1n8, FIELD_FIELDS2, FIELD_SQRT2;
|
|
3877
3700
|
var init_modular2 = __esm(() => {
|
|
3878
3701
|
init_utils4();
|
|
3879
3702
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3703
|
+
_0n7 = /* @__PURE__ */ BigInt(0);
|
|
3704
|
+
_1n8 = /* @__PURE__ */ BigInt(1);
|
|
3705
|
+
_2n5 = /* @__PURE__ */ BigInt(2);
|
|
3706
|
+
_3n2 = /* @__PURE__ */ BigInt(3);
|
|
3884
3707
|
_4n4 = /* @__PURE__ */ BigInt(4);
|
|
3885
3708
|
_5n3 = /* @__PURE__ */ BigInt(5);
|
|
3886
3709
|
_7n3 = /* @__PURE__ */ BigInt(7);
|
|
@@ -3938,7 +3761,7 @@ function calcOffsets(n, window, wOpts) {
|
|
|
3938
3761
|
let nextN = n >> shiftBy;
|
|
3939
3762
|
if (wbits > windowSize) {
|
|
3940
3763
|
wbits -= maxNumber;
|
|
3941
|
-
nextN +=
|
|
3764
|
+
nextN += _1n9;
|
|
3942
3765
|
}
|
|
3943
3766
|
const offsetStart = window * windowSize;
|
|
3944
3767
|
const offset = offsetStart + Math.abs(wbits) - 1;
|
|
@@ -3952,7 +3775,7 @@ function getW(P) {
|
|
|
3952
3775
|
return pointWindowSizes2.get(P) || 1;
|
|
3953
3776
|
}
|
|
3954
3777
|
function assert0(n) {
|
|
3955
|
-
if (n !==
|
|
3778
|
+
if (n !== _0n8)
|
|
3956
3779
|
throw new Error("invalid wNAF");
|
|
3957
3780
|
}
|
|
3958
3781
|
|
|
@@ -3969,11 +3792,11 @@ class wNAF {
|
|
|
3969
3792
|
}
|
|
3970
3793
|
_unsafeLadder(elm, n, p = this.ZERO) {
|
|
3971
3794
|
let d = elm;
|
|
3972
|
-
while (n >
|
|
3973
|
-
if (n &
|
|
3795
|
+
while (n > _0n8) {
|
|
3796
|
+
if (n & _1n9)
|
|
3974
3797
|
p = p.add(d);
|
|
3975
3798
|
d = d.double();
|
|
3976
|
-
n >>=
|
|
3799
|
+
n >>= _1n9;
|
|
3977
3800
|
}
|
|
3978
3801
|
return p;
|
|
3979
3802
|
}
|
|
@@ -4014,7 +3837,7 @@ class wNAF {
|
|
|
4014
3837
|
wNAFUnsafe(W, precomputes, n, acc = this.ZERO) {
|
|
4015
3838
|
const wo = calcWOpts(W, this.bits);
|
|
4016
3839
|
for (let window = 0;window < wo.windows; window++) {
|
|
4017
|
-
if (n ===
|
|
3840
|
+
if (n === _0n8)
|
|
4018
3841
|
break;
|
|
4019
3842
|
const { nextN, offset, isZero, isNeg } = calcOffsets(n, window, wo);
|
|
4020
3843
|
n = nextN;
|
|
@@ -4076,7 +3899,7 @@ function createCurveFields2(type, CURVE, curveOpts = {}, FpFnLE) {
|
|
|
4076
3899
|
throw new Error(`expected valid ${type} CURVE object`);
|
|
4077
3900
|
for (const p of ["p", "n", "h"]) {
|
|
4078
3901
|
const val = CURVE[p];
|
|
4079
|
-
if (!(typeof val === "bigint" && val >
|
|
3902
|
+
if (!(typeof val === "bigint" && val > _0n8))
|
|
4080
3903
|
throw new Error(`CURVE.${p} must be positive bigint`);
|
|
4081
3904
|
}
|
|
4082
3905
|
const Fp2 = createField2(CURVE.p, curveOpts.Fp, FpFnLE);
|
|
@@ -4096,13 +3919,13 @@ function createKeygen2(randomSecretKey, getPublicKey) {
|
|
|
4096
3919
|
return { secretKey, publicKey: getPublicKey(secretKey) };
|
|
4097
3920
|
};
|
|
4098
3921
|
}
|
|
4099
|
-
var
|
|
3922
|
+
var _0n8, _1n9, pointPrecomputes, pointWindowSizes2;
|
|
4100
3923
|
var init_curve2 = __esm(() => {
|
|
4101
3924
|
init_utils4();
|
|
4102
3925
|
init_modular2();
|
|
4103
3926
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
4104
|
-
|
|
4105
|
-
|
|
3927
|
+
_0n8 = /* @__PURE__ */ BigInt(0);
|
|
3928
|
+
_1n9 = /* @__PURE__ */ BigInt(1);
|
|
4106
3929
|
pointPrecomputes = new WeakMap;
|
|
4107
3930
|
pointWindowSizes2 = new WeakMap;
|
|
4108
3931
|
});
|
|
@@ -4122,19 +3945,19 @@ function edwards2(params, extraOpts = {}) {
|
|
|
4122
3945
|
let CURVE = validated.CURVE;
|
|
4123
3946
|
const { h: cofactor } = CURVE;
|
|
4124
3947
|
validateObject2(opts, {}, { uvRatio: "function" });
|
|
4125
|
-
const MASK =
|
|
3948
|
+
const MASK = _2n6 << BigInt(Fn.BYTES * 8) - _1n10;
|
|
4126
3949
|
const modP = (n) => Fp2.create(n);
|
|
4127
3950
|
const uvRatio2 = opts.uvRatio === undefined ? (u, v) => {
|
|
4128
3951
|
try {
|
|
4129
3952
|
return { isValid: true, value: Fp2.sqrt(Fp2.div(u, v)) };
|
|
4130
3953
|
} catch (e) {
|
|
4131
|
-
return { isValid: false, value:
|
|
3954
|
+
return { isValid: false, value: _0n9 };
|
|
4132
3955
|
}
|
|
4133
3956
|
} : opts.uvRatio;
|
|
4134
3957
|
if (!isEdValidXY2(Fp2, CURVE, CURVE.Gx, CURVE.Gy))
|
|
4135
3958
|
throw new Error("bad curve params: generator point");
|
|
4136
3959
|
function acoord(title, n, banZero = false) {
|
|
4137
|
-
const min = banZero ?
|
|
3960
|
+
const min = banZero ? _1n10 : _0n9;
|
|
4138
3961
|
aInRange2("coordinate " + title, n, min, MASK);
|
|
4139
3962
|
return n;
|
|
4140
3963
|
}
|
|
@@ -4144,8 +3967,8 @@ function edwards2(params, extraOpts = {}) {
|
|
|
4144
3967
|
}
|
|
4145
3968
|
|
|
4146
3969
|
class Point {
|
|
4147
|
-
static BASE = new Point(CURVE.Gx, CURVE.Gy,
|
|
4148
|
-
static ZERO = new Point(
|
|
3970
|
+
static BASE = new Point(CURVE.Gx, CURVE.Gy, _1n10, modP(CURVE.Gx * CURVE.Gy));
|
|
3971
|
+
static ZERO = new Point(_0n9, _1n10, _1n10, _0n9);
|
|
4149
3972
|
static Fp = Fp2;
|
|
4150
3973
|
static Fn = Fn;
|
|
4151
3974
|
X;
|
|
@@ -4168,7 +3991,7 @@ function edwards2(params, extraOpts = {}) {
|
|
|
4168
3991
|
const { x, y } = p || {};
|
|
4169
3992
|
acoord("x", x);
|
|
4170
3993
|
acoord("y", y);
|
|
4171
|
-
return new Point(x, y,
|
|
3994
|
+
return new Point(x, y, _1n10, modP(x * y));
|
|
4172
3995
|
}
|
|
4173
3996
|
static fromBytes(bytes, zip215 = false) {
|
|
4174
3997
|
const len = Fp2.BYTES;
|
|
@@ -4180,16 +4003,16 @@ function edwards2(params, extraOpts = {}) {
|
|
|
4180
4003
|
normed[len - 1] = lastByte & ~128;
|
|
4181
4004
|
const y = bytesToNumberLE2(normed);
|
|
4182
4005
|
const max = zip215 ? MASK : Fp2.ORDER;
|
|
4183
|
-
aInRange2("point.y", y,
|
|
4006
|
+
aInRange2("point.y", y, _0n9, max);
|
|
4184
4007
|
const y2 = modP(y * y);
|
|
4185
|
-
const u = modP(y2 -
|
|
4008
|
+
const u = modP(y2 - _1n10);
|
|
4186
4009
|
const v = modP(d * y2 - a);
|
|
4187
4010
|
let { isValid, value: x } = uvRatio2(u, v);
|
|
4188
4011
|
if (!isValid)
|
|
4189
4012
|
throw new Error("bad point: invalid y coordinate");
|
|
4190
|
-
const isXOdd = (x &
|
|
4013
|
+
const isXOdd = (x & _1n10) === _1n10;
|
|
4191
4014
|
const isLastByteOdd = (lastByte & 128) !== 0;
|
|
4192
|
-
if (!zip215 && x ===
|
|
4015
|
+
if (!zip215 && x === _0n9 && isLastByteOdd)
|
|
4193
4016
|
throw new Error("bad point: x=0 and x_0=1");
|
|
4194
4017
|
if (isLastByteOdd !== isXOdd)
|
|
4195
4018
|
x = modP(-x);
|
|
@@ -4207,7 +4030,7 @@ function edwards2(params, extraOpts = {}) {
|
|
|
4207
4030
|
precompute(windowSize = 8, isLazy = true) {
|
|
4208
4031
|
wnaf.createCache(this, windowSize);
|
|
4209
4032
|
if (!isLazy)
|
|
4210
|
-
this.multiply(
|
|
4033
|
+
this.multiply(_2n6);
|
|
4211
4034
|
return this;
|
|
4212
4035
|
}
|
|
4213
4036
|
assertValidity() {
|
|
@@ -4251,7 +4074,7 @@ function edwards2(params, extraOpts = {}) {
|
|
|
4251
4074
|
const { X: X1, Y: Y1, Z: Z1 } = this;
|
|
4252
4075
|
const A = modP(X1 * X1);
|
|
4253
4076
|
const B = modP(Y1 * Y1);
|
|
4254
|
-
const C = modP(
|
|
4077
|
+
const C = modP(_2n6 * modP(Z1 * Z1));
|
|
4255
4078
|
const D2 = modP(a * A);
|
|
4256
4079
|
const x1y1 = X1 + Y1;
|
|
4257
4080
|
const E = modP(modP(x1y1 * x1y1) - A - B);
|
|
@@ -4296,9 +4119,9 @@ function edwards2(params, extraOpts = {}) {
|
|
|
4296
4119
|
multiplyUnsafe(scalar) {
|
|
4297
4120
|
if (!Fn.isValid(scalar))
|
|
4298
4121
|
throw new RangeError("invalid scalar: expected 0 <= sc < curve.n");
|
|
4299
|
-
if (scalar ===
|
|
4122
|
+
if (scalar === _0n9)
|
|
4300
4123
|
return Point.ZERO;
|
|
4301
|
-
if (this.is0() || scalar ===
|
|
4124
|
+
if (this.is0() || scalar === _1n10)
|
|
4302
4125
|
return this;
|
|
4303
4126
|
return wnaf.unsafe(this, scalar, (p) => normalizeZ2(Point, p));
|
|
4304
4127
|
}
|
|
@@ -4319,20 +4142,20 @@ function edwards2(params, extraOpts = {}) {
|
|
|
4319
4142
|
const y = modP(Y * iz);
|
|
4320
4143
|
const zz = Fp2.mul(Z, iz);
|
|
4321
4144
|
if (is0)
|
|
4322
|
-
return { x:
|
|
4323
|
-
if (zz !==
|
|
4145
|
+
return { x: _0n9, y: _1n10 };
|
|
4146
|
+
if (zz !== _1n10)
|
|
4324
4147
|
throw new Error("invZ was invalid");
|
|
4325
4148
|
return { x, y };
|
|
4326
4149
|
}
|
|
4327
4150
|
clearCofactor() {
|
|
4328
|
-
if (cofactor ===
|
|
4151
|
+
if (cofactor === _1n10)
|
|
4329
4152
|
return this;
|
|
4330
4153
|
return this.multiplyUnsafe(cofactor);
|
|
4331
4154
|
}
|
|
4332
4155
|
toBytes() {
|
|
4333
4156
|
const { x, y } = this.toAffine();
|
|
4334
4157
|
const bytes = Fp2.toBytes(y);
|
|
4335
|
-
bytes[bytes.length - 1] |= x &
|
|
4158
|
+
bytes[bytes.length - 1] |= x & _1n10 ? 128 : 0;
|
|
4336
4159
|
return bytes;
|
|
4337
4160
|
}
|
|
4338
4161
|
toHex() {
|
|
@@ -4417,19 +4240,19 @@ class PrimeEdwardsPoint2 {
|
|
|
4417
4240
|
return this;
|
|
4418
4241
|
}
|
|
4419
4242
|
}
|
|
4420
|
-
var
|
|
4243
|
+
var _0n9, _1n10, _2n6, _8n5;
|
|
4421
4244
|
var init_edwards2 = __esm(() => {
|
|
4422
4245
|
init_utils4();
|
|
4423
4246
|
init_curve2();
|
|
4424
4247
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4248
|
+
_0n9 = /* @__PURE__ */ BigInt(0);
|
|
4249
|
+
_1n10 = /* @__PURE__ */ BigInt(1);
|
|
4250
|
+
_2n6 = /* @__PURE__ */ BigInt(2);
|
|
4428
4251
|
_8n5 = /* @__PURE__ */ BigInt(8);
|
|
4429
4252
|
});
|
|
4430
4253
|
|
|
4431
4254
|
// ../node_modules/.bun/@noble+curves@2.2.0/node_modules/@noble/curves/abstract/montgomery.js
|
|
4432
|
-
function
|
|
4255
|
+
function validateOpts2(curve) {
|
|
4433
4256
|
validateObject2(curve, {
|
|
4434
4257
|
P: "bigint",
|
|
4435
4258
|
type: "string",
|
|
@@ -4440,8 +4263,8 @@ function validateOpts3(curve) {
|
|
|
4440
4263
|
});
|
|
4441
4264
|
return Object.freeze({ ...curve });
|
|
4442
4265
|
}
|
|
4443
|
-
function
|
|
4444
|
-
const CURVE =
|
|
4266
|
+
function montgomery(curveDef) {
|
|
4267
|
+
const CURVE = validateOpts2(curveDef);
|
|
4445
4268
|
const { P, type, adjustScalarBytes: adjustScalarBytes2, powPminus2, randomBytes: rand } = CURVE;
|
|
4446
4269
|
const is25519 = type === "x25519";
|
|
4447
4270
|
if (!is25519 && type !== "x448")
|
|
@@ -4451,9 +4274,9 @@ function montgomery2(curveDef) {
|
|
|
4451
4274
|
const fieldLen = is25519 ? 32 : 56;
|
|
4452
4275
|
const Gu = is25519 ? BigInt(9) : BigInt(5);
|
|
4453
4276
|
const a24 = is25519 ? BigInt(121665) : BigInt(39081);
|
|
4454
|
-
const minScalar = is25519 ?
|
|
4455
|
-
const maxAdded = is25519 ? BigInt(8) *
|
|
4456
|
-
const maxScalar = minScalar + maxAdded +
|
|
4277
|
+
const minScalar = is25519 ? _2n7 ** BigInt(254) : _2n7 ** BigInt(447);
|
|
4278
|
+
const maxAdded = is25519 ? BigInt(8) * _2n7 ** BigInt(251) - _1n11 : BigInt(4) * _2n7 ** BigInt(445) - _1n11;
|
|
4279
|
+
const maxScalar = minScalar + maxAdded + _1n11;
|
|
4457
4280
|
const modP = (n) => mod2(n, P);
|
|
4458
4281
|
const GuBytes = encodeU(Gu);
|
|
4459
4282
|
function encodeU(u) {
|
|
@@ -4470,7 +4293,7 @@ function montgomery2(curveDef) {
|
|
|
4470
4293
|
}
|
|
4471
4294
|
function scalarMult(scalar, u) {
|
|
4472
4295
|
const pu = montgomeryLadder(decodeU(u), decodeScalar(scalar));
|
|
4473
|
-
if (pu ===
|
|
4296
|
+
if (pu === _0n10)
|
|
4474
4297
|
throw new Error("invalid private or public key received");
|
|
4475
4298
|
return encodeU(pu);
|
|
4476
4299
|
}
|
|
@@ -4479,27 +4302,27 @@ function montgomery2(curveDef) {
|
|
|
4479
4302
|
}
|
|
4480
4303
|
const getPublicKey = scalarMultBase;
|
|
4481
4304
|
const getSharedSecret = scalarMult;
|
|
4482
|
-
function
|
|
4305
|
+
function cswap(swap, x_2, x_3) {
|
|
4483
4306
|
const dummy = modP(swap * (x_2 - x_3));
|
|
4484
4307
|
x_2 = modP(x_2 - dummy);
|
|
4485
4308
|
x_3 = modP(x_3 + dummy);
|
|
4486
4309
|
return { x_2, x_3 };
|
|
4487
4310
|
}
|
|
4488
4311
|
function montgomeryLadder(u, scalar) {
|
|
4489
|
-
aInRange2("u", u,
|
|
4312
|
+
aInRange2("u", u, _0n10, P);
|
|
4490
4313
|
aInRange2("scalar", scalar, minScalar, maxScalar);
|
|
4491
4314
|
const k = scalar;
|
|
4492
4315
|
const x_1 = u;
|
|
4493
|
-
let x_2 =
|
|
4494
|
-
let z_2 =
|
|
4316
|
+
let x_2 = _1n11;
|
|
4317
|
+
let z_2 = _0n10;
|
|
4495
4318
|
let x_3 = u;
|
|
4496
|
-
let z_3 =
|
|
4497
|
-
let swap =
|
|
4498
|
-
for (let t = BigInt(montgomeryBits - 1);t >=
|
|
4499
|
-
const k_t = k >> t &
|
|
4319
|
+
let z_3 = _1n11;
|
|
4320
|
+
let swap = _0n10;
|
|
4321
|
+
for (let t = BigInt(montgomeryBits - 1);t >= _0n10; t--) {
|
|
4322
|
+
const k_t = k >> t & _1n11;
|
|
4500
4323
|
swap ^= k_t;
|
|
4501
|
-
({ x_2, x_3 } =
|
|
4502
|
-
({ x_2: z_2, x_3: z_3 } =
|
|
4324
|
+
({ x_2, x_3 } = cswap(swap, x_2, x_3));
|
|
4325
|
+
({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3));
|
|
4503
4326
|
swap = k_t;
|
|
4504
4327
|
const A = x_2 + z_2;
|
|
4505
4328
|
const AA = modP(A * A);
|
|
@@ -4517,8 +4340,8 @@ function montgomery2(curveDef) {
|
|
|
4517
4340
|
x_2 = modP(AA * BB);
|
|
4518
4341
|
z_2 = modP(E * (AA + modP(a24 * E)));
|
|
4519
4342
|
}
|
|
4520
|
-
({ x_2, x_3 } =
|
|
4521
|
-
({ x_2: z_2, x_3: z_3 } =
|
|
4343
|
+
({ x_2, x_3 } = cswap(swap, x_2, x_3));
|
|
4344
|
+
({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3));
|
|
4522
4345
|
const z2 = powPminus2(z_2);
|
|
4523
4346
|
return modP(x_2 * z2);
|
|
4524
4347
|
}
|
|
@@ -4546,15 +4369,15 @@ function montgomery2(curveDef) {
|
|
|
4546
4369
|
lengths
|
|
4547
4370
|
});
|
|
4548
4371
|
}
|
|
4549
|
-
var
|
|
4550
|
-
var
|
|
4372
|
+
var _0n10, _1n11, _2n7;
|
|
4373
|
+
var init_montgomery = __esm(() => {
|
|
4551
4374
|
init_utils4();
|
|
4552
4375
|
init_curve2();
|
|
4553
4376
|
init_modular2();
|
|
4554
4377
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4378
|
+
_0n10 = BigInt(0);
|
|
4379
|
+
_1n11 = BigInt(1);
|
|
4380
|
+
_2n7 = BigInt(2);
|
|
4558
4381
|
});
|
|
4559
4382
|
|
|
4560
4383
|
// ../node_modules/.bun/@noble+hashes@2.2.0/node_modules/@noble/hashes/hmac.js
|
|
@@ -5330,8 +5153,8 @@ function ed25519_pow_2_252_32(x) {
|
|
|
5330
5153
|
const P = ed25519_CURVE_p2;
|
|
5331
5154
|
const x2 = x * x % P;
|
|
5332
5155
|
const b2 = x2 * x % P;
|
|
5333
|
-
const b4 = pow22(b2,
|
|
5334
|
-
const b5 = pow22(b4,
|
|
5156
|
+
const b4 = pow22(b2, _2n8, P) * b2 % P;
|
|
5157
|
+
const b5 = pow22(b4, _1n12, P) * x % P;
|
|
5335
5158
|
const b10 = pow22(b5, _5n4, P) * b5 % P;
|
|
5336
5159
|
const b20 = pow22(b10, _10n, P) * b10 % P;
|
|
5337
5160
|
const b40 = pow22(b20, _20n, P) * b20 % P;
|
|
@@ -5339,7 +5162,7 @@ function ed25519_pow_2_252_32(x) {
|
|
|
5339
5162
|
const b160 = pow22(b80, _80n, P) * b80 % P;
|
|
5340
5163
|
const b240 = pow22(b160, _80n, P) * b80 % P;
|
|
5341
5164
|
const b250 = pow22(b240, _10n, P) * b10 % P;
|
|
5342
|
-
const pow_p_5_8 = pow22(b250,
|
|
5165
|
+
const pow_p_5_8 = pow22(b250, _2n8, P) * x % P;
|
|
5343
5166
|
return { pow_p_5_8, b2 };
|
|
5344
5167
|
}
|
|
5345
5168
|
function adjustScalarBytes2(bytes) {
|
|
@@ -5373,7 +5196,7 @@ function calcElligatorRistrettoMap(r0) {
|
|
|
5373
5196
|
const P = ed25519_CURVE_p2;
|
|
5374
5197
|
const mod3 = (n) => Fp2.create(n);
|
|
5375
5198
|
const r = mod3(SQRT_M1 * r0 * r0);
|
|
5376
|
-
const Ns = mod3((r +
|
|
5199
|
+
const Ns = mod3((r + _1n12) * ONE_MINUS_D_SQ);
|
|
5377
5200
|
let c = BigInt(-1);
|
|
5378
5201
|
const D2 = mod3((c - d * r) * mod3(r + d));
|
|
5379
5202
|
let { isValid: Ns_D_is_sq, value: s } = uvRatio2(Ns, D2);
|
|
@@ -5384,28 +5207,28 @@ function calcElligatorRistrettoMap(r0) {
|
|
|
5384
5207
|
s = s_;
|
|
5385
5208
|
if (!Ns_D_is_sq)
|
|
5386
5209
|
c = r;
|
|
5387
|
-
const Nt = mod3(c * (r -
|
|
5210
|
+
const Nt = mod3(c * (r - _1n12) * D_MINUS_ONE_SQ - D2);
|
|
5388
5211
|
const s2 = s * s;
|
|
5389
5212
|
const W0 = mod3((s + s) * D2);
|
|
5390
5213
|
const W1 = mod3(Nt * SQRT_AD_MINUS_ONE);
|
|
5391
|
-
const W2 = mod3(
|
|
5392
|
-
const W3 = mod3(
|
|
5214
|
+
const W2 = mod3(_1n12 - s2);
|
|
5215
|
+
const W3 = mod3(_1n12 + s2);
|
|
5393
5216
|
return new ed25519_Point2(mod3(W0 * W3), mod3(W2 * W1), mod3(W1 * W3), mod3(W0 * W2));
|
|
5394
5217
|
}
|
|
5395
|
-
var
|
|
5218
|
+
var _0n11, _1n12, _2n8, _3n3, _5n4, _8n6, ed25519_CURVE_p2, ed25519_CURVE2, ED25519_SQRT_M12, ed25519_Point2, Fp2, Fn, x25519, SQRT_M1, SQRT_AD_MINUS_ONE, INVSQRT_A_MINUS_D, ONE_MINUS_D_SQ, D_MINUS_ONE_SQ, invertSqrt = (number) => uvRatio2(_1n12, number), MAX_255B, bytes255ToNumberLE = (bytes) => Fp2.create(bytesToNumberLE2(bytes) & MAX_255B), _RistrettoPoint, ristretto255_hasher;
|
|
5396
5219
|
var init_ed255192 = __esm(() => {
|
|
5397
5220
|
init_sha22();
|
|
5398
5221
|
init_utils3();
|
|
5399
5222
|
init_edwards2();
|
|
5400
5223
|
init_hash_to_curve();
|
|
5401
5224
|
init_modular2();
|
|
5402
|
-
|
|
5225
|
+
init_montgomery();
|
|
5403
5226
|
init_utils4();
|
|
5404
5227
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5228
|
+
_0n11 = /* @__PURE__ */ BigInt(0);
|
|
5229
|
+
_1n12 = /* @__PURE__ */ BigInt(1);
|
|
5230
|
+
_2n8 = /* @__PURE__ */ BigInt(2);
|
|
5231
|
+
_3n3 = /* @__PURE__ */ BigInt(3);
|
|
5409
5232
|
_5n4 = /* @__PURE__ */ BigInt(5);
|
|
5410
5233
|
_8n6 = /* @__PURE__ */ BigInt(8);
|
|
5411
5234
|
ed25519_CURVE_p2 = /* @__PURE__ */ BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed");
|
|
@@ -5422,14 +5245,14 @@ var init_ed255192 = __esm(() => {
|
|
|
5422
5245
|
ed25519_Point2 = /* @__PURE__ */ edwards2(ed25519_CURVE2, { uvRatio: uvRatio2 });
|
|
5423
5246
|
Fp2 = /* @__PURE__ */ (() => ed25519_Point2.Fp)();
|
|
5424
5247
|
Fn = /* @__PURE__ */ (() => ed25519_Point2.Fn)();
|
|
5425
|
-
|
|
5248
|
+
x25519 = /* @__PURE__ */ (() => {
|
|
5426
5249
|
const P = ed25519_CURVE_p2;
|
|
5427
|
-
return
|
|
5250
|
+
return montgomery({
|
|
5428
5251
|
P,
|
|
5429
5252
|
type: "x25519",
|
|
5430
5253
|
powPminus2: (x) => {
|
|
5431
5254
|
const { pow_p_5_8, b2 } = ed25519_pow_2_252_32(x);
|
|
5432
|
-
return mod2(pow22(pow_p_5_8,
|
|
5255
|
+
return mod2(pow22(pow_p_5_8, _3n3, P) * b2, P);
|
|
5433
5256
|
},
|
|
5434
5257
|
adjustScalarBytes: adjustScalarBytes2
|
|
5435
5258
|
});
|
|
@@ -5467,8 +5290,8 @@ var init_ed255192 = __esm(() => {
|
|
|
5467
5290
|
if (!equalBytes(Fp2.toBytes(s), bytes) || isNegativeLE2(s, P))
|
|
5468
5291
|
throw new Error("invalid ristretto255 encoding 1");
|
|
5469
5292
|
const s2 = mod3(s * s);
|
|
5470
|
-
const u1 = mod3(
|
|
5471
|
-
const u2 = mod3(
|
|
5293
|
+
const u1 = mod3(_1n12 + a * s2);
|
|
5294
|
+
const u2 = mod3(_1n12 - a * s2);
|
|
5472
5295
|
const u1_2 = mod3(u1 * u1);
|
|
5473
5296
|
const u2_2 = mod3(u2 * u2);
|
|
5474
5297
|
const v = mod3(a * d * u1_2 - u2_2);
|
|
@@ -5480,9 +5303,9 @@ var init_ed255192 = __esm(() => {
|
|
|
5480
5303
|
x = mod3(-x);
|
|
5481
5304
|
const y = mod3(u1 * Dy);
|
|
5482
5305
|
const t = mod3(x * y);
|
|
5483
|
-
if (!isValid || isNegativeLE2(t, P) || y ===
|
|
5306
|
+
if (!isValid || isNegativeLE2(t, P) || y === _0n11)
|
|
5484
5307
|
throw new Error("invalid ristretto255 encoding 2");
|
|
5485
|
-
return new _RistrettoPoint(new ed25519_Point2(x, y,
|
|
5308
|
+
return new _RistrettoPoint(new ed25519_Point2(x, y, _1n12, t));
|
|
5486
5309
|
}
|
|
5487
5310
|
static fromHex(hex) {
|
|
5488
5311
|
return _RistrettoPoint.fromBytes(hexToBytes3(hex));
|
|
@@ -6052,7 +5875,7 @@ var init_hybrid = __esm(() => {
|
|
|
6052
5875
|
init_ml_kem();
|
|
6053
5876
|
init_utils5();
|
|
6054
5877
|
/*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
|
|
6055
|
-
x25519kem = /* @__PURE__ */ ecdhKem(
|
|
5878
|
+
x25519kem = /* @__PURE__ */ ecdhKem(x25519);
|
|
6056
5879
|
ml_kem768_x25519 = /* @__PURE__ */ (() => combineKEMS(32, 32, expandSeedXof(shake256), (pk, ct, ss) => sha3_256(concatBytes4(ss[0], ss[1], ct[1], pk[1], asciiToBytes("\\.//^\\"))), ml_kem768, x25519kem))();
|
|
6057
5880
|
});
|
|
6058
5881
|
|
|
@@ -6066,29 +5889,68 @@ function deriveKeysFromSeed(seed) {
|
|
|
6066
5889
|
const edPublic = ed25519.getPublicKey(edSecret);
|
|
6067
5890
|
const mlKeys = ml_dsa65.keygen(mlSeed);
|
|
6068
5891
|
mlSeed.fill(0);
|
|
6069
|
-
|
|
5892
|
+
const result = {
|
|
6070
5893
|
ed25519: { publicKey: b64(edPublic), secretKey: b64(edSecret) },
|
|
6071
5894
|
mlDsa: { publicKey: b64(mlKeys.publicKey), secretKey: b64(mlKeys.secretKey) }
|
|
6072
5895
|
};
|
|
5896
|
+
edSecret.fill(0);
|
|
5897
|
+
mlKeys.secretKey.fill(0);
|
|
5898
|
+
return result;
|
|
5899
|
+
}
|
|
5900
|
+
function exactBytes(value, bytes) {
|
|
5901
|
+
if (typeof value !== "string" || value.length !== encodedLength(bytes) || !BASE64URL.test(value))
|
|
5902
|
+
return null;
|
|
5903
|
+
try {
|
|
5904
|
+
const decoded = un64(value);
|
|
5905
|
+
return decoded.length === bytes && b64(decoded) === value ? decoded : null;
|
|
5906
|
+
} catch {
|
|
5907
|
+
return null;
|
|
5908
|
+
}
|
|
5909
|
+
}
|
|
5910
|
+
function validIdentity(value) {
|
|
5911
|
+
return typeof value === "string" && value.length >= 1 && value.length <= 128 && !/[^A-Za-z0-9_.:@/-]/.test(value) && !/[\0\r\n]/.test(value);
|
|
5912
|
+
}
|
|
5913
|
+
function validResponsePublicKey(value) {
|
|
5914
|
+
return exactBytes(value, RESPONSE_PUBLIC_BYTES) !== null;
|
|
6073
5915
|
}
|
|
6074
5916
|
function generateResponseRecipient() {
|
|
6075
5917
|
const pair = ml_kem768_x25519.keygen();
|
|
6076
5918
|
return { publicKey: b64(pair.publicKey), secretKey: b64(pair.secretKey) };
|
|
6077
5919
|
}
|
|
6078
5920
|
async function openResponse(recipientSecretKey, requestBinding, envelope) {
|
|
6079
|
-
if (
|
|
5921
|
+
if (!requestBinding || requestBinding.length > 16384 || /[\0\r\n]/.test(requestBinding)) {
|
|
5922
|
+
throw new Error("response: malformed request binding");
|
|
5923
|
+
}
|
|
5924
|
+
if (envelope?.version !== 1 || envelope.suite !== RESPONSE_SEALING_SUITE) {
|
|
6080
5925
|
throw new Error("response: unsupported sealed response");
|
|
6081
|
-
|
|
5926
|
+
}
|
|
5927
|
+
const kemCiphertext = exactBytes(envelope.kemCiphertext, RESPONSE_CIPHERTEXT_BYTES);
|
|
5928
|
+
const nonce = exactBytes(envelope.nonce, 12);
|
|
5929
|
+
if (!kemCiphertext || !nonce || typeof envelope.ciphertext !== "string" || envelope.ciphertext.length < encodedLength(16) || envelope.ciphertext.length > encodedLength(16 * 1024 * 1024) || !BASE64URL.test(envelope.ciphertext)) {
|
|
5930
|
+
throw new Error("response: malformed sealed response");
|
|
5931
|
+
}
|
|
5932
|
+
let encodedCiphertext;
|
|
5933
|
+
try {
|
|
5934
|
+
encodedCiphertext = un64(envelope.ciphertext);
|
|
5935
|
+
} catch {
|
|
5936
|
+
throw new Error("response: malformed sealed response");
|
|
5937
|
+
}
|
|
5938
|
+
if (encodedCiphertext.length < 16 || encodedCiphertext.length > 16 * 1024 * 1024 || b64(encodedCiphertext) !== envelope.ciphertext) {
|
|
5939
|
+
throw new Error("response: malformed sealed response");
|
|
5940
|
+
}
|
|
5941
|
+
const sharedSecret = ml_kem768_x25519.decapsulate(kemCiphertext, exactBytes(recipientSecretKey, RESPONSE_SECRET_BYTES) ?? (() => {
|
|
5942
|
+
throw new Error("response: malformed recipient secret key");
|
|
5943
|
+
})());
|
|
6082
5944
|
const rawKey = responseKey(sharedSecret);
|
|
6083
5945
|
sharedSecret.fill(0);
|
|
6084
5946
|
const key = await crypto.subtle.importKey("raw", new Uint8Array(rawKey), "AES-GCM", false, ["decrypt"]);
|
|
6085
5947
|
rawKey.fill(0);
|
|
6086
5948
|
const decrypted = new Uint8Array(await crypto.subtle.decrypt({
|
|
6087
5949
|
name: "AES-GCM",
|
|
6088
|
-
iv: new Uint8Array(
|
|
5950
|
+
iv: new Uint8Array(nonce),
|
|
6089
5951
|
additionalData: new Uint8Array(ENCODER.encode(requestBinding)),
|
|
6090
5952
|
tagLength: 128
|
|
6091
|
-
}, key, new Uint8Array(
|
|
5953
|
+
}, key, new Uint8Array(encodedCiphertext)));
|
|
6092
5954
|
try {
|
|
6093
5955
|
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
6094
5956
|
} finally {
|
|
@@ -6099,9 +5961,15 @@ function encodeSignatureHeader(envelope) {
|
|
|
6099
5961
|
return b64(ENCODER.encode(JSON.stringify(SIGNATURE_FIELDS(envelope))));
|
|
6100
5962
|
}
|
|
6101
5963
|
function canonicalString(args) {
|
|
5964
|
+
if (!validIdentity(args.identity) || !/^[A-Za-z][A-Za-z0-9-]{0,31}$/.test(args.method) || /[\0\r\n]/.test(args.path) || /[\0\r\n]/.test(args.query) || !exactBytes(args.nonce, 16) || args.responseKey !== undefined && !validResponsePublicKey(args.responseKey)) {
|
|
5965
|
+
throw new Error("identity: malformed canonical request");
|
|
5966
|
+
}
|
|
6102
5967
|
const body = typeof args.body === "string" ? ENCODER.encode(args.body) : args.body;
|
|
6103
5968
|
const digest = Array.from(sha256(body), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
6104
5969
|
const fields = [
|
|
5970
|
+
"forgezero/request-signature/v1",
|
|
5971
|
+
REQUEST_SIGNATURE_SUITE,
|
|
5972
|
+
args.identity,
|
|
6105
5973
|
args.method.toUpperCase(),
|
|
6106
5974
|
args.path,
|
|
6107
5975
|
args.query ?? "",
|
|
@@ -6109,16 +5977,17 @@ function canonicalString(args) {
|
|
|
6109
5977
|
args.nonce,
|
|
6110
5978
|
digest
|
|
6111
5979
|
];
|
|
6112
|
-
|
|
6113
|
-
fields.push(args.responseKey);
|
|
5980
|
+
fields.push(args.responseKey ?? "-");
|
|
6114
5981
|
return fields.join(`
|
|
6115
5982
|
`);
|
|
6116
5983
|
}
|
|
6117
5984
|
function signRequest(keys, nodeKey, args) {
|
|
6118
5985
|
const timestamp = Math.floor(Date.now() / 1000);
|
|
6119
5986
|
const nonce = b64(randomBytes6(16));
|
|
6120
|
-
const message = ENCODER.encode(canonicalString({ ...args, query: args.query ?? "", timestamp, nonce }));
|
|
5987
|
+
const message = ENCODER.encode(canonicalString({ ...args, identity: nodeKey, query: args.query ?? "", timestamp, nonce }));
|
|
6121
5988
|
return {
|
|
5989
|
+
version: 1,
|
|
5990
|
+
suite: REQUEST_SIGNATURE_SUITE,
|
|
6122
5991
|
nodeKey,
|
|
6123
5992
|
timestamp,
|
|
6124
5993
|
nonce,
|
|
@@ -6126,7 +5995,13 @@ function signRequest(keys, nodeKey, args) {
|
|
|
6126
5995
|
mlDsaSignature: b64(ml_dsa65.sign(message, un64(keys.mlDsa.secretKey)))
|
|
6127
5996
|
};
|
|
6128
5997
|
}
|
|
6129
|
-
var ENCODER, b64, un64, randomBytes6 = (length) => crypto.getRandomValues(new Uint8Array(length)), RESPONSE_KEY_HEADER = "x-fz-response-key",
|
|
5998
|
+
var ENCODER, b64, un64, randomBytes6 = (length) => crypto.getRandomValues(new Uint8Array(length)), RESPONSE_KEY_HEADER = "x-fz-response-key", REQUEST_SIGNATURE_SUITE = "ed25519+ml-dsa-65", RESPONSE_SEALING_SUITE = "ml-kem-768+x25519/aes-256-gcm", BASE64URL, encodedLength = (bytes) => Math.ceil(bytes * 4 / 3), length = (value, name) => {
|
|
5999
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
6000
|
+
throw new Error(`identity: ${name} length unavailable`);
|
|
6001
|
+
return value;
|
|
6002
|
+
}, ED_PUBLIC_BYTES, ED_SIGNATURE_BYTES, ML_PUBLIC_BYTES, ML_SIGNATURE_BYTES, RESPONSE_PUBLIC_BYTES, RESPONSE_SECRET_BYTES, RESPONSE_CIPHERTEXT_BYTES, responseKey = (sharedSecret) => hkdf(sha256, sharedSecret, undefined, ENCODER.encode("forgezero/response/ml-kem-768+x25519/v1"), 32), SIGNATURE_FIELDS = (envelope) => ({
|
|
6003
|
+
version: envelope.version,
|
|
6004
|
+
suite: envelope.suite,
|
|
6130
6005
|
timestamp: envelope.timestamp,
|
|
6131
6006
|
nonce: envelope.nonce,
|
|
6132
6007
|
edSignature: envelope.edSignature,
|
|
@@ -6142,6 +6017,14 @@ var init_identity = __esm(() => {
|
|
|
6142
6017
|
ENCODER = new TextEncoder;
|
|
6143
6018
|
b64 = toBase64Url;
|
|
6144
6019
|
un64 = fromBase64Url;
|
|
6020
|
+
BASE64URL = /^[A-Za-z0-9_-]+$/;
|
|
6021
|
+
ED_PUBLIC_BYTES = length(ed25519.lengths.publicKey, "Ed25519 public key");
|
|
6022
|
+
ED_SIGNATURE_BYTES = length(ed25519.lengths.signature, "Ed25519 signature");
|
|
6023
|
+
ML_PUBLIC_BYTES = length(ml_dsa65.lengths.publicKey, "ML-DSA-65 public key");
|
|
6024
|
+
ML_SIGNATURE_BYTES = length(ml_dsa65.lengths.signature, "ML-DSA-65 signature");
|
|
6025
|
+
RESPONSE_PUBLIC_BYTES = length(ml_kem768_x25519.lengths.publicKey, "hybrid response public key");
|
|
6026
|
+
RESPONSE_SECRET_BYTES = length(ml_kem768_x25519.lengths.secretKey, "hybrid response secret key");
|
|
6027
|
+
RESPONSE_CIPHERTEXT_BYTES = length(ml_kem768_x25519.lengths.cipherText, "hybrid response ciphertext");
|
|
6145
6028
|
});
|
|
6146
6029
|
|
|
6147
6030
|
// ../vault/dist/index.js
|
|
@@ -6189,8 +6072,8 @@ function signerFromApiKey(secret) {
|
|
|
6189
6072
|
seed.fill(0);
|
|
6190
6073
|
return {
|
|
6191
6074
|
keyId,
|
|
6192
|
-
async sign(
|
|
6193
|
-
return encodeSignatureHeader(signRequest(keys, keyId,
|
|
6075
|
+
async sign(request) {
|
|
6076
|
+
return encodeSignatureHeader(signRequest(keys, keyId, request));
|
|
6194
6077
|
}
|
|
6195
6078
|
};
|
|
6196
6079
|
}
|
|
@@ -6459,13 +6342,13 @@ var VaultError, runtimeEnvironment = () => typeof process !== "undefined" && pro
|
|
|
6459
6342
|
throw new VaultError("API_KEY_MALFORMED", "FORGEZERO_API_KEY is malformed.");
|
|
6460
6343
|
}
|
|
6461
6344
|
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
6462
|
-
}, requestAgentOnce = (socketPath,
|
|
6345
|
+
}, requestAgentOnce = (socketPath, request) => new Promise((resolveRequest, reject) => {
|
|
6463
6346
|
if (typeof process === "undefined" || typeof process.getBuiltinModule !== "function") {
|
|
6464
6347
|
reject(new VaultError("MANAGED_RUNTIME_UNAVAILABLE", "The managed agent socket needs a Node or Bun server runtime."));
|
|
6465
6348
|
return;
|
|
6466
6349
|
}
|
|
6467
6350
|
const net = process.getBuiltinModule("node:net");
|
|
6468
|
-
const socket = net.connect(socketPath, () => socket.write(`${JSON.stringify(
|
|
6351
|
+
const socket = net.connect(socketPath, () => socket.write(`${JSON.stringify(request)}
|
|
6469
6352
|
`));
|
|
6470
6353
|
let buffer = "";
|
|
6471
6354
|
socket.setTimeout(15000, () => {
|
|
@@ -6492,11 +6375,11 @@ var VaultError, runtimeEnvironment = () => typeof process !== "undefined" && pro
|
|
|
6492
6375
|
});
|
|
6493
6376
|
socket.on("end", () => reject(new VaultError("AGENT_UNAVAILABLE", "The local ForgeZero agent closed during a supervised handover.")));
|
|
6494
6377
|
socket.on("error", (cause) => reject(new VaultError("AGENT_UNAVAILABLE", cause.message)));
|
|
6495
|
-
}), managedAgentTransport = async (socketPath,
|
|
6378
|
+
}), managedAgentTransport = async (socketPath, request) => {
|
|
6496
6379
|
let last;
|
|
6497
6380
|
for (let attempt = 0;attempt < 5; attempt += 1) {
|
|
6498
6381
|
try {
|
|
6499
|
-
return await requestAgentOnce(socketPath,
|
|
6382
|
+
return await requestAgentOnce(socketPath, request);
|
|
6500
6383
|
} catch (cause) {
|
|
6501
6384
|
last = cause;
|
|
6502
6385
|
if (cause?.code !== "AGENT_UNAVAILABLE" || attempt === 4)
|
|
@@ -6582,128 +6465,6 @@ function thresholdMode(id) {
|
|
|
6582
6465
|
return THRESHOLD_MODES.find((mode) => mode.id === id);
|
|
6583
6466
|
}
|
|
6584
6467
|
|
|
6585
|
-
// ../runtime/dist/ssh-agent.js
|
|
6586
|
-
import { Socket } from "net";
|
|
6587
|
-
import { createHash, hkdfSync } from "crypto";
|
|
6588
|
-
class SshAgentError extends Error {
|
|
6589
|
-
}
|
|
6590
|
-
var SSH_AGENTC_REQUEST_IDENTITIES = 11;
|
|
6591
|
-
var SSH_AGENT_IDENTITIES_ANSWER = 12;
|
|
6592
|
-
var SSH_AGENTC_SIGN_REQUEST = 13;
|
|
6593
|
-
var SSH_AGENT_SIGN_RESPONSE = 14;
|
|
6594
|
-
var CUSTODY_CHALLENGE = Buffer.from("forgezero/custody/ssh-agent/v1", "utf8");
|
|
6595
|
-
function readString(buffer, offset) {
|
|
6596
|
-
const length = buffer.readUInt32BE(offset);
|
|
6597
|
-
const start = offset + 4;
|
|
6598
|
-
return [buffer.subarray(start, start + length), start + length];
|
|
6599
|
-
}
|
|
6600
|
-
function writeString(value) {
|
|
6601
|
-
const length = Buffer.alloc(4);
|
|
6602
|
-
length.writeUInt32BE(value.length);
|
|
6603
|
-
return Buffer.concat([length, value]);
|
|
6604
|
-
}
|
|
6605
|
-
function frame(payload) {
|
|
6606
|
-
const length = Buffer.alloc(4);
|
|
6607
|
-
length.writeUInt32BE(payload.length);
|
|
6608
|
-
return Buffer.concat([length, payload]);
|
|
6609
|
-
}
|
|
6610
|
-
var AGENT_TIMEOUT_MS = 3000;
|
|
6611
|
-
async function request(socketPath, payload) {
|
|
6612
|
-
return new Promise((resolve, reject) => {
|
|
6613
|
-
const socket = new Socket;
|
|
6614
|
-
const chunks = [];
|
|
6615
|
-
let expected = null;
|
|
6616
|
-
let settled = false;
|
|
6617
|
-
const fail = (message) => {
|
|
6618
|
-
if (settled)
|
|
6619
|
-
return;
|
|
6620
|
-
settled = true;
|
|
6621
|
-
socket.destroy();
|
|
6622
|
-
reject(new SshAgentError(message));
|
|
6623
|
-
};
|
|
6624
|
-
socket.setTimeout(AGENT_TIMEOUT_MS, () => fail("SSH_AGENT_TIMEOUT"));
|
|
6625
|
-
socket.on("error", () => fail("SSH_AGENT_UNREACHABLE"));
|
|
6626
|
-
socket.on("connect", () => socket.write(frame(payload)));
|
|
6627
|
-
socket.on("data", (chunk) => {
|
|
6628
|
-
chunks.push(chunk);
|
|
6629
|
-
const all = Buffer.concat(chunks);
|
|
6630
|
-
if (expected === null && all.length >= 4)
|
|
6631
|
-
expected = all.readUInt32BE(0);
|
|
6632
|
-
if (expected !== null && all.length >= expected + 4) {
|
|
6633
|
-
if (settled)
|
|
6634
|
-
return;
|
|
6635
|
-
settled = true;
|
|
6636
|
-
socket.end();
|
|
6637
|
-
resolve(all.subarray(4, expected + 4));
|
|
6638
|
-
}
|
|
6639
|
-
});
|
|
6640
|
-
socket.on("close", () => {
|
|
6641
|
-
if (expected === null)
|
|
6642
|
-
fail("SSH_AGENT_CLOSED_EARLY");
|
|
6643
|
-
});
|
|
6644
|
-
socket.connect(socketPath);
|
|
6645
|
-
});
|
|
6646
|
-
}
|
|
6647
|
-
function agentSocket(explicit) {
|
|
6648
|
-
const path = explicit ?? process.env.SSH_AUTH_SOCK;
|
|
6649
|
-
if (!path)
|
|
6650
|
-
throw new SshAgentError("SSH_AUTH_SOCK_NOT_SET");
|
|
6651
|
-
return path;
|
|
6652
|
-
}
|
|
6653
|
-
async function listIdentities(socketPath) {
|
|
6654
|
-
const response = await request(agentSocket(socketPath), Buffer.from([SSH_AGENTC_REQUEST_IDENTITIES]));
|
|
6655
|
-
if (response[0] !== SSH_AGENT_IDENTITIES_ANSWER) {
|
|
6656
|
-
throw new SshAgentError("SSH_AGENT_BAD_RESPONSE");
|
|
6657
|
-
}
|
|
6658
|
-
const count = response.readUInt32BE(1);
|
|
6659
|
-
const identities = [];
|
|
6660
|
-
let offset = 5;
|
|
6661
|
-
for (let index = 0;index < count; index += 1) {
|
|
6662
|
-
const [blob, afterBlob] = readString(response, offset);
|
|
6663
|
-
const [comment, afterComment] = readString(response, afterBlob);
|
|
6664
|
-
offset = afterComment;
|
|
6665
|
-
const [type] = readString(blob, 0);
|
|
6666
|
-
identities.push({
|
|
6667
|
-
blob,
|
|
6668
|
-
comment: comment.toString("utf8"),
|
|
6669
|
-
type: type.toString("utf8"),
|
|
6670
|
-
fingerprint: `SHA256:${createHash("sha256").update(blob).digest("base64").replace(/=+$/, "")}`
|
|
6671
|
-
});
|
|
6672
|
-
}
|
|
6673
|
-
return identities;
|
|
6674
|
-
}
|
|
6675
|
-
async function listCustodyIdentities(socketPath) {
|
|
6676
|
-
return (await listIdentities(socketPath)).filter((id) => id.type === "ssh-ed25519");
|
|
6677
|
-
}
|
|
6678
|
-
async function signWithIdentity(identity, data, socketPath) {
|
|
6679
|
-
const wrapped = await sign(identity.blob, Buffer.from(data), socketPath);
|
|
6680
|
-
const [raw] = readString(wrapped, readString(wrapped, 0)[1]);
|
|
6681
|
-
return new Uint8Array(raw);
|
|
6682
|
-
}
|
|
6683
|
-
async function sign(blob, data, socketPath) {
|
|
6684
|
-
const payload = Buffer.concat([
|
|
6685
|
-
Buffer.from([SSH_AGENTC_SIGN_REQUEST]),
|
|
6686
|
-
writeString(blob),
|
|
6687
|
-
writeString(data),
|
|
6688
|
-
Buffer.alloc(4)
|
|
6689
|
-
]);
|
|
6690
|
-
const response = await request(agentSocket(socketPath), payload);
|
|
6691
|
-
if (response[0] !== SSH_AGENT_SIGN_RESPONSE) {
|
|
6692
|
-
throw new SshAgentError("SSH_AGENT_SIGN_REFUSED");
|
|
6693
|
-
}
|
|
6694
|
-
const [signature] = readString(response, 1);
|
|
6695
|
-
return signature;
|
|
6696
|
-
}
|
|
6697
|
-
async function deriveCustodyKey(identity, socketPath) {
|
|
6698
|
-
if (identity.type !== "ssh-ed25519") {
|
|
6699
|
-
throw new SshAgentError("SSH_KEY_TYPE_UNSUPPORTED");
|
|
6700
|
-
}
|
|
6701
|
-
const signature = await sign(identity.blob, CUSTODY_CHALLENGE, socketPath);
|
|
6702
|
-
if (signature.length < 32)
|
|
6703
|
-
throw new SshAgentError("SSH_AGENT_SIGNATURE_TOO_SHORT");
|
|
6704
|
-
return new Uint8Array(hkdfSync("sha256", signature, identity.blob, Buffer.from("forgezero/custody/ssh-key/v1", "utf8"), 32));
|
|
6705
|
-
}
|
|
6706
|
-
|
|
6707
6468
|
// src/cli/run.ts
|
|
6708
6469
|
class RunError extends Error {
|
|
6709
6470
|
code;
|
|
@@ -6818,7 +6579,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
6818
6579
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
6819
6580
|
|
|
6820
6581
|
// src/version.ts
|
|
6821
|
-
var VERSION2 = "0.1.
|
|
6582
|
+
var VERSION2 = "0.1.40";
|
|
6822
6583
|
|
|
6823
6584
|
// src/software.ts
|
|
6824
6585
|
var BUN_INSTALLER_SHA256 = "bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd";
|
|
@@ -6848,8 +6609,8 @@ var UBUNTU_2604_X64 = [
|
|
|
6848
6609
|
},
|
|
6849
6610
|
{
|
|
6850
6611
|
requirement: { id: "arangodb", version: "3.11.14" },
|
|
6851
|
-
check: `arangod --version 2>/dev/null | head -1 | grep -q '3.11.14'`,
|
|
6852
|
-
install: `tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT; ` + `curl -fsSL 'https://download.arangodb.com/arangodb311/DEBIAN/amd64/arangodb3_3.11.14-1_amd64.deb' -o "$tmp/arangodb.deb"; ` + `echo "${ARANGO_SHA256} $tmp/arangodb.deb" | sha256sum -c -; ` + `DEBIAN_FRONTEND=noninteractive dpkg -i "$tmp/arangodb.deb" >/dev/null 2>&1 || ` + `DEBIAN_FRONTEND=noninteractive apt-get -y -f install`
|
|
6612
|
+
check: `arangod --version 2>/dev/null | head -1 | grep -q '3.11.14' && ` + `! systemctl is-active --quiet arangodb3.service && ` + `! systemctl is-enabled --quiet arangodb3.service`,
|
|
6613
|
+
install: `tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT; ` + `curl -fsSL 'https://download.arangodb.com/arangodb311/DEBIAN/amd64/arangodb3_3.11.14-1_amd64.deb' -o "$tmp/arangodb.deb"; ` + `echo "${ARANGO_SHA256} $tmp/arangodb.deb" | sha256sum -c -; ` + `DEBIAN_FRONTEND=noninteractive dpkg -i "$tmp/arangodb.deb" >/dev/null 2>&1 || ` + `DEBIAN_FRONTEND=noninteractive apt-get -y -f install; ` + `systemctl disable --now arangodb3.service`
|
|
6853
6614
|
},
|
|
6854
6615
|
{
|
|
6855
6616
|
requirement: { id: "cloudflared", version: "2026.7.3" },
|
|
@@ -7931,27 +7692,6 @@ function renderPlan(plan) {
|
|
|
7931
7692
|
`);
|
|
7932
7693
|
}
|
|
7933
7694
|
|
|
7934
|
-
// src/cli/custody.ts
|
|
7935
|
-
var PROBE_TIMEOUT_MS = 2000;
|
|
7936
|
-
function withTimeout(work, ms, label) {
|
|
7937
|
-
return Promise.race([
|
|
7938
|
-
work,
|
|
7939
|
-
new Promise((_, reject) => setTimeout(() => reject(new SshAgentError(`SSH_AGENT_TIMEOUT:${label}`)), ms))
|
|
7940
|
-
]);
|
|
7941
|
-
}
|
|
7942
|
-
async function usableIdentities(socketPath) {
|
|
7943
|
-
const candidates = await listCustodyIdentities(socketPath);
|
|
7944
|
-
const usable = [];
|
|
7945
|
-
for (const identity of candidates) {
|
|
7946
|
-
const started = Date.now();
|
|
7947
|
-
try {
|
|
7948
|
-
await withTimeout(deriveCustodyKey(identity, socketPath), PROBE_TIMEOUT_MS, identity.fingerprint);
|
|
7949
|
-
usable.push({ ...identity, responseMs: Date.now() - started });
|
|
7950
|
-
} catch {}
|
|
7951
|
-
}
|
|
7952
|
-
return usable;
|
|
7953
|
-
}
|
|
7954
|
-
|
|
7955
7695
|
// ../node_modules/.bun/@noble+ciphers@2.2.0/node_modules/@noble/ciphers/utils.js
|
|
7956
7696
|
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
|
|
7957
7697
|
function isBytes5(a) {
|
|
@@ -7967,13 +7707,13 @@ function anumber5(n) {
|
|
|
7967
7707
|
if (!Number.isSafeInteger(n) || n < 0)
|
|
7968
7708
|
throw new RangeError("positive integer expected, got " + n);
|
|
7969
7709
|
}
|
|
7970
|
-
function abytes5(value,
|
|
7710
|
+
function abytes5(value, length2, title = "") {
|
|
7971
7711
|
const bytes = isBytes5(value);
|
|
7972
7712
|
const len = value?.length;
|
|
7973
|
-
const needsLen =
|
|
7974
|
-
if (!bytes || needsLen && len !==
|
|
7713
|
+
const needsLen = length2 !== undefined;
|
|
7714
|
+
if (!bytes || needsLen && len !== length2) {
|
|
7975
7715
|
const prefix = title && `"${title}" `;
|
|
7976
|
-
const ofLen = needsLen ? ` of length ${
|
|
7716
|
+
const ofLen = needsLen ? ` of length ${length2}` : "";
|
|
7977
7717
|
const got = bytes ? `length=${len}` : `type=${typeof value}`;
|
|
7978
7718
|
const message = prefix + "expected Uint8Array" + ofLen + ", got " + got;
|
|
7979
7719
|
if (!bytes)
|
|
@@ -8463,9 +8203,9 @@ var gcm = /* @__PURE__ */ wrapCipher({ blockSize: 16, nonceLength: 12, tagLength
|
|
|
8463
8203
|
});
|
|
8464
8204
|
|
|
8465
8205
|
// ../runtime/dist/custody-share.js
|
|
8466
|
-
init_ed25519();
|
|
8467
8206
|
init_hkdf();
|
|
8468
8207
|
init_sha22();
|
|
8208
|
+
init_hybrid();
|
|
8469
8209
|
|
|
8470
8210
|
// ../node_modules/.bun/@noble+hashes@2.2.0/node_modules/@noble/hashes/pbkdf2.js
|
|
8471
8211
|
init_hmac();
|
|
@@ -11090,8 +10830,8 @@ var fromBase64 = (value) => {
|
|
|
11090
10830
|
return bytes;
|
|
11091
10831
|
};
|
|
11092
10832
|
var utf8 = (value) => new TextEncoder().encode(value);
|
|
11093
|
-
function deriveKey(secret, salt, info,
|
|
11094
|
-
return hkdf(sha256, secret, salt, utf8(info),
|
|
10833
|
+
function deriveKey(secret, salt, info, length2 = KEY_BYTES) {
|
|
10834
|
+
return hkdf(sha256, secret, salt, utf8(info), length2);
|
|
11095
10835
|
}
|
|
11096
10836
|
function openWithKey(key, box, aad) {
|
|
11097
10837
|
if (key.length !== KEY_BYTES)
|
|
@@ -11100,28 +10840,43 @@ function openWithKey(key, box, aad) {
|
|
|
11100
10840
|
throw new Error(`custody: unknown algorithm ${box.alg}`);
|
|
11101
10841
|
return gcm(key, fromBase64(box.nonce), utf8(aad)).decrypt(fromBase64(box.ciphertext));
|
|
11102
10842
|
}
|
|
11103
|
-
var WRAP_INFO = "forgezero:custody:wrap:
|
|
11104
|
-
var
|
|
11105
|
-
|
|
11106
|
-
const out = new Uint8Array(left.length + right.length);
|
|
11107
|
-
out.set(left, 0);
|
|
11108
|
-
out.set(right, left.length);
|
|
11109
|
-
return out;
|
|
11110
|
-
}
|
|
10843
|
+
var WRAP_INFO = "forgezero:custody:wrap:ml-kem-768+x25519:v2";
|
|
10844
|
+
var WRAP_SEED_SALT = utf8("forgezero:custody:wrapkey:ml-kem-768+x25519:v2");
|
|
10845
|
+
var wrapKey = (shared) => hkdf(sha256, shared, undefined, utf8(WRAP_INFO), 32);
|
|
11111
10846
|
function openFromKey(recipientSecretKey, box, aad) {
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
|
|
11115
|
-
|
|
10847
|
+
if (box?.version !== 2 || typeof box.kemCiphertext !== "string") {
|
|
10848
|
+
throw new Error("custody: unsupported sealed-to-key envelope");
|
|
10849
|
+
}
|
|
10850
|
+
if (recipientSecretKey.length !== ml_kem768_x25519.lengths.secretKey) {
|
|
10851
|
+
throw new Error("custody: invalid hybrid recipient secret key");
|
|
10852
|
+
}
|
|
10853
|
+
const ciphertext = fromBase64(box.kemCiphertext);
|
|
10854
|
+
if (ciphertext.length !== ml_kem768_x25519.lengths.cipherText) {
|
|
10855
|
+
throw new Error("custody: invalid hybrid KEM ciphertext");
|
|
10856
|
+
}
|
|
10857
|
+
const shared = Uint8Array.from(ml_kem768_x25519.decapsulate(ciphertext, recipientSecretKey));
|
|
10858
|
+
const key = wrapKey(shared);
|
|
11116
10859
|
try {
|
|
11117
10860
|
return openWithKey(key, box, aad);
|
|
11118
10861
|
} finally {
|
|
11119
10862
|
key.fill(0);
|
|
10863
|
+
shared.fill(0);
|
|
11120
10864
|
}
|
|
11121
10865
|
}
|
|
11122
10866
|
function wrappingKeyPair(factorMaterial, info) {
|
|
11123
|
-
|
|
11124
|
-
|
|
10867
|
+
if (factorMaterial.length < 32) {
|
|
10868
|
+
throw new Error("custody: wrapping factor material must be at least 32 bytes");
|
|
10869
|
+
}
|
|
10870
|
+
const seed = hkdf(sha256, factorMaterial, WRAP_SEED_SALT, utf8(info), 32);
|
|
10871
|
+
try {
|
|
10872
|
+
const pair = ml_kem768_x25519.keygen(seed);
|
|
10873
|
+
return {
|
|
10874
|
+
secretKey: Uint8Array.from(pair.secretKey),
|
|
10875
|
+
publicKey: Uint8Array.from(pair.publicKey)
|
|
10876
|
+
};
|
|
10877
|
+
} finally {
|
|
10878
|
+
seed.fill(0);
|
|
10879
|
+
}
|
|
11125
10880
|
}
|
|
11126
10881
|
var ENCODER2 = new TextEncoder;
|
|
11127
10882
|
var PHRASE_WORDS = 24;
|
|
@@ -11366,7 +11121,7 @@ function defaultProjectContext(root = process.cwd()) {
|
|
|
11366
11121
|
"Update a truth source instead of copying architecture or progress into another document.",
|
|
11367
11122
|
"Never report a feature as complete without running its declared verification."
|
|
11368
11123
|
],
|
|
11369
|
-
nonAuthoritative: ["audit/"]
|
|
11124
|
+
nonAuthoritative: ["docs/audit/"]
|
|
11370
11125
|
};
|
|
11371
11126
|
}
|
|
11372
11127
|
function renderProjectContext(manifest) {
|
|
@@ -11516,7 +11271,7 @@ function checkProjectContext(rootInput) {
|
|
|
11516
11271
|
}
|
|
11517
11272
|
|
|
11518
11273
|
// src/deploy-file.ts
|
|
11519
|
-
import { createHash
|
|
11274
|
+
import { createHash } from "crypto";
|
|
11520
11275
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
11521
11276
|
import { basename, join as join3 } from "path";
|
|
11522
11277
|
|
|
@@ -11766,7 +11521,7 @@ var stable = (value) => {
|
|
|
11766
11521
|
return JSON.stringify(value);
|
|
11767
11522
|
};
|
|
11768
11523
|
function deployDefinitionDigest(definition) {
|
|
11769
|
-
return `sha256:${
|
|
11524
|
+
return `sha256:${createHash("sha256").update(stable(definition)).digest("hex")}`;
|
|
11770
11525
|
}
|
|
11771
11526
|
var safeName = (value) => {
|
|
11772
11527
|
const normalized = value.toLowerCase().replace(/^@[^/]+\//, "").replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
|
|
@@ -12061,7 +11816,7 @@ function removeSession(api, realm, path = defaultSessionPath()) {
|
|
|
12061
11816
|
}
|
|
12062
11817
|
|
|
12063
11818
|
// src/bootstrap.ts
|
|
12064
|
-
import { createHash as
|
|
11819
|
+
import { createHash as createHash2, createHmac, randomBytes as randomBytes7 } from "crypto";
|
|
12065
11820
|
import {
|
|
12066
11821
|
chmodSync as chmodSync2,
|
|
12067
11822
|
existsSync as existsSync4,
|
|
@@ -12295,6 +12050,8 @@ function renderPlatformApiUnits(input) {
|
|
|
12295
12050
|
throw new Error("Blue and green ports must differ.");
|
|
12296
12051
|
const credentials = input.credentials.map((credential) => `LoadCredentialEncrypted=${credential.name}:${credential.encryptedPath}`).join(`
|
|
12297
12052
|
`);
|
|
12053
|
+
const capacityEnvironment = input.capacityEnvironmentFile ? `EnvironmentFile=-${input.capacityEnvironmentFile}
|
|
12054
|
+
` : "";
|
|
12298
12055
|
const template = `[Unit]
|
|
12299
12056
|
Description=ForgeZero (%i slot)
|
|
12300
12057
|
After=network-online.target ${input.collectorUnit}
|
|
@@ -12307,7 +12064,7 @@ WorkingDirectory=${input.slotsDirectory}/%i
|
|
|
12307
12064
|
Environment=NODE_ENV=production
|
|
12308
12065
|
Environment=FZ_SLOT=%i
|
|
12309
12066
|
EnvironmentFile=${input.sharedEnvironmentFile}
|
|
12310
|
-
${credentials}
|
|
12067
|
+
${capacityEnvironment}${credentials}
|
|
12311
12068
|
ExecStart=/usr/local/bin/bun run ${input.slotsDirectory}/%i/src/index.ts
|
|
12312
12069
|
Restart=always
|
|
12313
12070
|
RestartSec=2
|
|
@@ -12343,16 +12100,24 @@ Environment=PORT=${input.greenPort}
|
|
|
12343
12100
|
function renderPlatformNginx(input) {
|
|
12344
12101
|
boundedInteger("publicPort", input.publicPort, 1024, 65535);
|
|
12345
12102
|
boundedInteger("initialSlotPort", input.initialSlotPort, 1024, 65535);
|
|
12103
|
+
const concurrencyLimit = boundedInteger("concurrencyLimit", input.concurrencyLimit ?? 256, 1, 1e6);
|
|
12104
|
+
boundedInteger("workerDrainSeconds", input.workerDrainSeconds ?? 35, 1, 300);
|
|
12346
12105
|
if (input.publicPort === input.initialSlotPort)
|
|
12347
12106
|
throw new Error("Edge and slot ports must differ.");
|
|
12348
12107
|
return {
|
|
12349
12108
|
upstream: `upstream forgezero { server 127.0.0.1:${input.initialSlotPort}; }
|
|
12350
12109
|
`,
|
|
12351
|
-
site: `
|
|
12110
|
+
site: `limit_conn_zone $server_name zone=forgezero_admission:10m;
|
|
12111
|
+
map $http_upgrade $forgezero_connection { default upgrade; '' close; }
|
|
12112
|
+
map $limit_conn_status $forgezero_retry_after { default ''; REJECTED 1; REJECTED_DRY_RUN 1; }
|
|
12113
|
+
server {
|
|
12352
12114
|
listen 127.0.0.1:${input.publicPort};
|
|
12353
12115
|
server_name _;
|
|
12354
|
-
|
|
12355
|
-
|
|
12116
|
+
limit_conn forgezero_admission ${concurrencyLimit};
|
|
12117
|
+
limit_conn_status 503;
|
|
12118
|
+
add_header Retry-After $forgezero_retry_after always;
|
|
12119
|
+
location ^~ /api/ { proxy_pass http://forgezero; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $forgezero_connection; proxy_read_timeout 3600s; }
|
|
12120
|
+
location ^~ /v1/ { proxy_pass http://forgezero; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $forgezero_connection; proxy_read_timeout 3600s; }
|
|
12356
12121
|
location / { return 404; }
|
|
12357
12122
|
}
|
|
12358
12123
|
`
|
|
@@ -12368,6 +12133,7 @@ function renderPlatformActivationFiles(input) {
|
|
|
12368
12133
|
if (input.bluePort === input.greenPort)
|
|
12369
12134
|
throw new Error("Activation slot ports must differ.");
|
|
12370
12135
|
boundedInteger("keepReleases", input.keepReleases, 2, 100);
|
|
12136
|
+
const drainDeadlineMs = boundedInteger("drainDeadlineMs", input.drainDeadlineMs ?? 35000, 1000, 300000);
|
|
12371
12137
|
if (!/^\/[A-Za-z0-9/_-]{1,128}$/.test(input.healthPath) || input.healthPath.includes("..")) {
|
|
12372
12138
|
throw new Error("Activation health path is malformed.");
|
|
12373
12139
|
}
|
|
@@ -12377,7 +12143,8 @@ function renderPlatformActivationFiles(input) {
|
|
|
12377
12143
|
`FZ_BLUE_PORT=${input.bluePort}`,
|
|
12378
12144
|
`FZ_GREEN_PORT=${input.greenPort}`,
|
|
12379
12145
|
`FZ_HEALTH_PATH=${input.healthPath}`,
|
|
12380
|
-
`FZ_KEEP_RELEASES=${input.keepReleases}
|
|
12146
|
+
`FZ_KEEP_RELEASES=${input.keepReleases}`,
|
|
12147
|
+
`FZ_DRAIN_DEADLINE_MS=${drainDeadlineMs}`
|
|
12381
12148
|
].join(`
|
|
12382
12149
|
`) + `
|
|
12383
12150
|
`;
|
|
@@ -12399,7 +12166,14 @@ if (( ! healthy )); then systemctl stop "forgezero@\${target}.service" || true;
|
|
|
12399
12166
|
upstream=/etc/nginx/conf.d/forgezero-upstream.conf; backup="$(mktemp -p /run forgezero-upstream.XXXXXX)"; [[ -f "$upstream" ]] && cp "$upstream" "$backup" || : >"$backup"
|
|
12400
12167
|
printf 'upstream forgezero { server 127.0.0.1:%s; }\\n' "$port" >"$upstream"
|
|
12401
12168
|
if ! nginx -t || ! nginx -s reload; then [[ -s "$backup" ]] && cp "$backup" "$upstream" || rm -f "$upstream"; rm -f "$backup"; systemctl stop "forgezero@\${target}.service" || true; [[ -n "$previous_target_link" && -d "$previous_target_link" ]] && ln -sfn "$previous_target_link" "$target_link" || rm -f "$target_link"; nginx -t >/dev/null 2>&1 && nginx -s reload || true; exit 1; fi
|
|
12402
|
-
rm -f "$backup"; printf '%s\\n' "$target" >"$slot_file"
|
|
12169
|
+
rm -f "$backup"; printf '%s\\n' "$target" >"$slot_file"
|
|
12170
|
+
# New nginx workers select the new slot after reload. Keep the old slot alive
|
|
12171
|
+
# while old workers drain in-flight requests and upgraded connections.
|
|
12172
|
+
if [[ -n "$previous_slot" && "$previous_slot" != "$target" ]]; then
|
|
12173
|
+
sleep_seconds="$(( (FZ_DRAIN_DEADLINE_MS + 999) / 1000 ))"
|
|
12174
|
+
sleep "$sleep_seconds"
|
|
12175
|
+
systemctl stop "forgezero@\${previous_slot}.service" || true
|
|
12176
|
+
fi
|
|
12403
12177
|
mapfile -t old < <(find "$releases" -mindepth 1 -maxdepth 1 -type d -printf '%T@ %p\\n' | sort -rn | tail -n "+$((FZ_KEEP_RELEASES + 1))" | cut -d' ' -f2-)
|
|
12404
12178
|
for path in "\${old[@]}"; do [[ "$path" == "$release" ]] || rm -rf -- "$path"; done
|
|
12405
12179
|
printf 'promoted %s on %s\\n' "$release" "$target"
|
|
@@ -12450,34 +12224,11 @@ function planLocalOtlpProof(endpoint, collectorUnit) {
|
|
|
12450
12224
|
|
|
12451
12225
|
// src/cloudflare-bootstrap.ts
|
|
12452
12226
|
import { constants } from "fs";
|
|
12453
|
-
import { chmod, lstat, mkdir, mkdtemp, open, rename, rm, stat, unlink } from "fs/promises";
|
|
12454
|
-
import { dirname as dirname3, join as join5, resolve as resolve2 } from "path";
|
|
12455
|
-
import { tmpdir } from "os";
|
|
12456
12227
|
import { randomUUID } from "crypto";
|
|
12457
|
-
import {
|
|
12228
|
+
import { chmod, lstat, mkdir, open, rename, stat, unlink } from "fs/promises";
|
|
12229
|
+
import { dirname as dirname3, join as join5, resolve as resolve2 } from "path";
|
|
12458
12230
|
|
|
12459
12231
|
// src/cloudflare-edge.ts
|
|
12460
|
-
import { isIP as isIP2 } from "net";
|
|
12461
|
-
function isPrivateDatabaseAddress(value) {
|
|
12462
|
-
const address = value.trim().toLowerCase();
|
|
12463
|
-
const family = isIP2(address);
|
|
12464
|
-
if (family === 4) {
|
|
12465
|
-
const [a, b] = address.split(".").map(Number);
|
|
12466
|
-
return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
|
|
12467
|
-
}
|
|
12468
|
-
if (family === 6) {
|
|
12469
|
-
const first = Number.parseInt(address.split(":", 1)[0], 16);
|
|
12470
|
-
return Number.isFinite(first) && (first & 65024) === 64512;
|
|
12471
|
-
}
|
|
12472
|
-
return false;
|
|
12473
|
-
}
|
|
12474
|
-
function privateDatabaseHostRoute(value) {
|
|
12475
|
-
const address = value.trim().toLowerCase();
|
|
12476
|
-
if (!isPrivateDatabaseAddress(address)) {
|
|
12477
|
-
throw new Error("database address must be an RFC 1918 IPv4 or unique-local IPv6 address");
|
|
12478
|
-
}
|
|
12479
|
-
return `${address}/${isIP2(address) === 4 ? 32 : 128}`;
|
|
12480
|
-
}
|
|
12481
12232
|
var endpoint = "https://api.cloudflare.com/client/v4";
|
|
12482
12233
|
async function cf(config, path, init = {}, fetcher = fetch) {
|
|
12483
12234
|
const response = await fetcher(`${endpoint}${path}`, {
|
|
@@ -12494,76 +12245,36 @@ async function cf(config, path, init = {}, fetcher = fetch) {
|
|
|
12494
12245
|
}
|
|
12495
12246
|
return body.result;
|
|
12496
12247
|
}
|
|
12497
|
-
async function ensureCloudflarePrivateRoute(config, fetcher = fetch) {
|
|
12498
|
-
const [address, prefixText, ...extra] = config.network.split("/");
|
|
12499
|
-
const family = isIP2(address ?? "");
|
|
12500
|
-
const prefix = Number(prefixText);
|
|
12501
|
-
if (extra.length > 0 || !family || !Number.isInteger(prefix) || prefix < 0 || prefix > (family === 4 ? 32 : 128)) {
|
|
12502
|
-
throw new Error("Cloudflare private route must be an explicit IPv4 or IPv6 CIDR");
|
|
12503
|
-
}
|
|
12504
|
-
const path = `/accounts/${config.accountId}/teamnet/routes`;
|
|
12505
|
-
const routes = await cf(config, path, {}, fetcher);
|
|
12506
|
-
const current = routes.find((route2) => !route2.deleted_at && route2.network === config.network && (route2.virtual_network_id ?? "") === (config.virtualNetworkId ?? ""));
|
|
12507
|
-
if (current) {
|
|
12508
|
-
if (current.tunnel_id !== config.tunnelId) {
|
|
12509
|
-
throw new Error(`private route ${config.network} already belongs to another Tunnel`);
|
|
12510
|
-
}
|
|
12511
|
-
return { route: current, created: false };
|
|
12512
|
-
}
|
|
12513
|
-
const route = await cf(config, path, {
|
|
12514
|
-
method: "POST",
|
|
12515
|
-
body: JSON.stringify({
|
|
12516
|
-
network: config.network,
|
|
12517
|
-
tunnel_id: config.tunnelId,
|
|
12518
|
-
comment: config.comment.slice(0, 100),
|
|
12519
|
-
...config.virtualNetworkId ? { virtual_network_id: config.virtualNetworkId } : {}
|
|
12520
|
-
})
|
|
12521
|
-
}, fetcher);
|
|
12522
|
-
return { route, created: true };
|
|
12523
|
-
}
|
|
12524
|
-
async function ensureCloudflarePrivateDatabaseRoute(config, fetcher = fetch) {
|
|
12525
|
-
return ensureCloudflarePrivateRoute({
|
|
12526
|
-
...config,
|
|
12527
|
-
network: privateDatabaseHostRoute(config.privateAddress)
|
|
12528
|
-
}, fetcher);
|
|
12529
|
-
}
|
|
12530
|
-
async function ensureCloudflareWarpDatabaseInclude(config, fetcher = fetch) {
|
|
12531
|
-
if (config.policyId && !/^[A-Za-z0-9-]{1,64}$/.test(config.policyId))
|
|
12532
|
-
throw new Error("Cloudflare WARP policy id is invalid");
|
|
12533
|
-
const route = privateDatabaseHostRoute(config.privateAddress);
|
|
12534
|
-
const policy = config.policyId ? `/${config.policyId}` : "";
|
|
12535
|
-
const path = `/accounts/${config.accountId}/devices/policy${policy}/include`;
|
|
12536
|
-
const entries = await cf(config, path, {}, fetcher);
|
|
12537
|
-
if (entries.some((entry) => entry.address === route))
|
|
12538
|
-
return { entries, created: false };
|
|
12539
|
-
const next = [...entries, { address: route, description: config.description.slice(0, 100) }];
|
|
12540
|
-
const updated = await cf(config, path, {
|
|
12541
|
-
method: "PUT",
|
|
12542
|
-
body: JSON.stringify(next)
|
|
12543
|
-
}, fetcher);
|
|
12544
|
-
return { entries: updated, created: true };
|
|
12545
|
-
}
|
|
12546
12248
|
async function configureCloudflareEdge(config, fetcher = fetch) {
|
|
12547
12249
|
const tunnelAuth = { apiToken: config.tunnelApiToken?.trim() || config.apiToken };
|
|
12548
12250
|
const dnsAuth = { apiToken: config.dnsApiToken?.trim() || config.apiToken };
|
|
12549
12251
|
const tunnelPath = `/accounts/${config.accountId}/cfd_tunnel/${config.tunnelId}/configurations`;
|
|
12550
|
-
const current = await cf(tunnelAuth, tunnelPath, {}, fetcher);
|
|
12551
|
-
const existing = current.config?.ingress ?? [];
|
|
12552
|
-
const catchAll = existing.filter((rule) => !("hostname" in rule));
|
|
12553
|
-
const otherHosts = existing.filter((rule) => ("hostname" in rule) && rule.hostname !== config.hostname);
|
|
12554
|
-
await cf(tunnelAuth, tunnelPath, {
|
|
12555
|
-
method: "PUT",
|
|
12556
|
-
body: JSON.stringify({ config: { ingress: [
|
|
12557
|
-
{ hostname: config.hostname, service: config.service },
|
|
12558
|
-
...otherHosts,
|
|
12559
|
-
...catchAll.length > 0 ? catchAll : [{ service: "http_status:404" }]
|
|
12560
|
-
] } })
|
|
12561
|
-
}, fetcher);
|
|
12562
12252
|
const dnsPath = `/zones/${config.zoneId}/dns_records`;
|
|
12563
|
-
const records = await
|
|
12253
|
+
const [current, records] = await Promise.all([
|
|
12254
|
+
cf(tunnelAuth, tunnelPath, {}, fetcher),
|
|
12255
|
+
cf(dnsAuth, `${dnsPath}?name=${encodeURIComponent(config.hostname)}&per_page=1000`, {}, fetcher)
|
|
12256
|
+
]);
|
|
12564
12257
|
if (records.length > 1) {
|
|
12565
12258
|
throw new Error(`Cloudflare DNS record for ${config.hostname} is ambiguous`);
|
|
12566
12259
|
}
|
|
12260
|
+
const existingRecord = records[0];
|
|
12261
|
+
if (existingRecord && (existingRecord.type !== "CNAME" || existingRecord.name && existingRecord.name.toLowerCase() !== config.hostname.toLowerCase())) {
|
|
12262
|
+
throw new Error(`Cloudflare DNS hostname ${config.hostname} is already owned by an incompatible ${existingRecord.type ?? "unknown"} record`);
|
|
12263
|
+
}
|
|
12264
|
+
const existing = current.config?.ingress ?? [];
|
|
12265
|
+
const catchAll = existing.filter((rule) => !("hostname" in rule));
|
|
12266
|
+
const otherHosts = existing.filter((rule) => ("hostname" in rule) && rule.hostname !== config.hostname);
|
|
12267
|
+
const desiredIngress = [
|
|
12268
|
+
{ hostname: config.hostname, service: config.service },
|
|
12269
|
+
...otherHosts,
|
|
12270
|
+
...catchAll.length > 0 ? catchAll : [{ service: "http_status:404" }]
|
|
12271
|
+
];
|
|
12272
|
+
if (JSON.stringify(existing) !== JSON.stringify(desiredIngress)) {
|
|
12273
|
+
await cf(tunnelAuth, tunnelPath, {
|
|
12274
|
+
method: "PUT",
|
|
12275
|
+
body: JSON.stringify({ config: { ingress: desiredIngress } })
|
|
12276
|
+
}, fetcher);
|
|
12277
|
+
}
|
|
12567
12278
|
const record2 = {
|
|
12568
12279
|
type: "CNAME",
|
|
12569
12280
|
name: config.hostname,
|
|
@@ -12571,233 +12282,13 @@ async function configureCloudflareEdge(config, fetcher = fetch) {
|
|
|
12571
12282
|
proxied: true,
|
|
12572
12283
|
ttl: 1
|
|
12573
12284
|
};
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
const groups = await cf(config, `/accounts/${config.accountId}/tokens/permission_groups?name=${encodeURIComponent(name)}` + "&scope=com.cloudflare.api.account", {}, fetcher);
|
|
12581
|
-
const matches = groups.filter((group) => group.name === name && group.scopes?.includes("com.cloudflare.api.account") && Boolean(group.id && /^[a-f0-9]{32}$/i.test(group.id)));
|
|
12582
|
-
if (matches.length !== 1) {
|
|
12583
|
-
throw new Error(`Cloudflare account token permission group ${name} is ${matches.length === 0 ? "missing" : "ambiguous"}`);
|
|
12584
|
-
}
|
|
12585
|
-
return { id: matches[0].id, name };
|
|
12586
|
-
};
|
|
12587
|
-
async function createCloudflareAccountRuntimeToken(config, fetcher = fetch) {
|
|
12588
|
-
if (!/^[a-f0-9]{32}$/i.test(config.accountId))
|
|
12589
|
-
throw new Error("Cloudflare account id is invalid");
|
|
12590
|
-
const name = config.name.trim();
|
|
12591
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]{0,119}$/.test(name)) {
|
|
12592
|
-
throw new Error("Cloudflare account runtime-token name is invalid");
|
|
12593
|
-
}
|
|
12594
|
-
const permissionNames = [...new Set(config.permissionNames)];
|
|
12595
|
-
if (permissionNames.length === 0)
|
|
12596
|
-
throw new Error("Cloudflare account runtime token needs a permission group");
|
|
12597
|
-
const permissionGroups = await Promise.all(permissionNames.map((permissionName) => exactAccountPermissionGroup(config, permissionName, fetcher)));
|
|
12598
|
-
const body = {
|
|
12599
|
-
name,
|
|
12600
|
-
policies: [{
|
|
12601
|
-
effect: "allow",
|
|
12602
|
-
permission_groups: permissionGroups.map(({ id: id2 }) => ({ id: id2 })),
|
|
12603
|
-
resources: { [`com.cloudflare.api.account.${config.accountId}`]: "*" }
|
|
12604
|
-
}]
|
|
12605
|
-
};
|
|
12606
|
-
const created = await cf(config, `/accounts/${config.accountId}/tokens`, { method: "POST", body: JSON.stringify(body) }, fetcher);
|
|
12607
|
-
if (!created.id || !/^[a-f0-9]{32}$/i.test(created.id) || !created.value || !/^[A-Za-z0-9._-]{40,80}$/.test(created.value)) {
|
|
12608
|
-
throw new Error("Cloudflare did not return the one-time account runtime-token id and value");
|
|
12609
|
-
}
|
|
12610
|
-
return { id: created.id, value: created.value, name, permissionNames };
|
|
12611
|
-
}
|
|
12612
|
-
async function ensureCloudflareAccessServiceToken(config, fetcher = fetch) {
|
|
12613
|
-
const name = config.name.trim();
|
|
12614
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]{0,99}$/.test(name)) {
|
|
12615
|
-
throw new Error("Cloudflare Access service-token name is invalid");
|
|
12616
|
-
}
|
|
12617
|
-
const path = `/accounts/${config.accountId}/access/service_tokens`;
|
|
12618
|
-
const tokens = await cf(config, `${path}?per_page=1000`, {}, fetcher);
|
|
12619
|
-
const matches = tokens.filter((token) => token.name === name);
|
|
12620
|
-
if (matches.length > 1)
|
|
12621
|
-
throw new Error(`Cloudflare Access service token ${name} is ambiguous`);
|
|
12622
|
-
if (matches[0]) {
|
|
12623
|
-
if (!config.existing || config.existing.tokenId !== matches[0].id || config.existing.clientId !== matches[0].client_id || !config.existing.clientSecret) {
|
|
12624
|
-
throw new Error(`Cloudflare Access service token ${name} exists but its one-time client secret was not supplied`);
|
|
12625
|
-
}
|
|
12626
|
-
return { credentials: config.existing, created: false };
|
|
12627
|
-
}
|
|
12628
|
-
const created = await cf(config, path, {
|
|
12629
|
-
method: "POST",
|
|
12630
|
-
body: JSON.stringify({ name, duration: config.duration ?? "8760h" })
|
|
12631
|
-
}, fetcher);
|
|
12632
|
-
if (!created.id || !created.client_id || !created.client_secret) {
|
|
12633
|
-
throw new Error("Cloudflare did not return the new Access service-token secret");
|
|
12634
|
-
}
|
|
12635
|
-
return {
|
|
12636
|
-
credentials: {
|
|
12637
|
-
tokenId: created.id,
|
|
12638
|
-
clientId: created.client_id,
|
|
12639
|
-
clientSecret: created.client_secret
|
|
12640
|
-
},
|
|
12641
|
-
created: true
|
|
12642
|
-
};
|
|
12643
|
-
}
|
|
12644
|
-
async function ensureCloudflareAccessPolicy(config, fetcher = fetch) {
|
|
12645
|
-
const path = `/accounts/${config.accountId}/access/policies`;
|
|
12646
|
-
const policies = await cf(config, `${path}?per_page=1000`, {}, fetcher);
|
|
12647
|
-
const matches = policies.filter((policy2) => policy2.name === config.name);
|
|
12648
|
-
if (matches.length > 1)
|
|
12649
|
-
throw new Error(`Cloudflare Access policy ${config.name} is ambiguous`);
|
|
12650
|
-
const desired = {
|
|
12651
|
-
name: config.name,
|
|
12652
|
-
decision: "non_identity",
|
|
12653
|
-
include: [{ service_token: { token_id: config.serviceTokenId } }]
|
|
12654
|
-
};
|
|
12655
|
-
const policy = await cf(config, matches[0] ? `${path}/${encodeURIComponent(matches[0].id)}` : path, {
|
|
12656
|
-
method: matches[0] ? "PUT" : "POST",
|
|
12657
|
-
body: JSON.stringify(desired)
|
|
12658
|
-
}, fetcher);
|
|
12659
|
-
return { policy, created: !matches[0] };
|
|
12660
|
-
}
|
|
12661
|
-
async function ensureCloudflareAccessApplication(config, fetcher = fetch) {
|
|
12662
|
-
const path = `/accounts/${config.accountId}/access/apps`;
|
|
12663
|
-
const applications = await cf(config, `${path}?per_page=1000`, {}, fetcher);
|
|
12664
|
-
const matches = applications.filter((application2) => application2.domain === config.hostname || application2.self_hosted_domains?.includes(config.hostname));
|
|
12665
|
-
if (matches.length > 1)
|
|
12666
|
-
throw new Error(`Cloudflare Access application for ${config.hostname} is ambiguous`);
|
|
12667
|
-
const desired = {
|
|
12668
|
-
name: config.name,
|
|
12669
|
-
type: "self_hosted",
|
|
12670
|
-
domain: config.hostname,
|
|
12671
|
-
session_duration: "24h",
|
|
12672
|
-
service_auth_401_redirect: true,
|
|
12673
|
-
policies: [{ id: config.policyId, precedence: 1 }]
|
|
12674
|
-
};
|
|
12675
|
-
const application = await cf(config, matches[0] ? `${path}/${encodeURIComponent(matches[0].id)}` : path, { method: matches[0] ? "PUT" : "POST", body: JSON.stringify(desired) }, fetcher);
|
|
12676
|
-
return { application, created: !matches[0] };
|
|
12677
|
-
}
|
|
12678
|
-
async function ensureCloudflareWarpEnrollmentApplication(config, fetcher = fetch) {
|
|
12679
|
-
const name = config.name.trim();
|
|
12680
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]{0,99}$/.test(name)) {
|
|
12681
|
-
throw new Error("Cloudflare WARP enrollment application name is invalid");
|
|
12682
|
-
}
|
|
12683
|
-
const path = `/accounts/${config.accountId}/access/apps`;
|
|
12684
|
-
const applications = await cf(config, `${path}?per_page=1000`, {}, fetcher);
|
|
12685
|
-
const matches = applications.filter((application2) => application2.type === "warp" || application2.name === name);
|
|
12686
|
-
if (matches.length > 1)
|
|
12687
|
-
throw new Error(`Cloudflare WARP enrollment application ${name} is ambiguous`);
|
|
12688
|
-
if (matches[0] && (matches[0].type !== "warp" || matches[0].name !== name)) {
|
|
12689
|
-
throw new Error(`Cloudflare Access application ${name} is not the owned WARP enrollment application`);
|
|
12690
|
-
}
|
|
12691
|
-
const desired = {
|
|
12692
|
-
name,
|
|
12693
|
-
type: "warp",
|
|
12694
|
-
policies: [{ id: config.policyId, precedence: 1 }]
|
|
12695
|
-
};
|
|
12696
|
-
const application = await cf(config, matches[0] ? `${path}/${encodeURIComponent(matches[0].id)}` : path, { method: matches[0] ? "PUT" : "POST", body: JSON.stringify(desired) }, fetcher);
|
|
12697
|
-
if (!application.id || application.type && application.type !== "warp") {
|
|
12698
|
-
throw new Error("Cloudflare did not return the WARP enrollment application");
|
|
12699
|
-
}
|
|
12700
|
-
return { application, created: !matches[0] };
|
|
12701
|
-
}
|
|
12702
|
-
async function ensureCloudflareVirtualNetwork(config, fetcher = fetch) {
|
|
12703
|
-
const name = config.name.trim();
|
|
12704
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]{0,99}$/.test(name))
|
|
12705
|
-
throw new Error("Cloudflare VNET name is invalid");
|
|
12706
|
-
const path = `/accounts/${config.accountId}/teamnet/virtual_networks`;
|
|
12707
|
-
const networks = await cf(config, `${path}?per_page=1000`, {}, fetcher);
|
|
12708
|
-
const matches = networks.filter((network) => !network.deleted_at && network.name === name);
|
|
12709
|
-
if (matches.length > 1)
|
|
12710
|
-
throw new Error(`Cloudflare VNET ${name} is ambiguous`);
|
|
12711
|
-
if (matches[0])
|
|
12712
|
-
return { virtualNetwork: matches[0], created: false };
|
|
12713
|
-
const virtualNetwork = await cf(config, path, {
|
|
12714
|
-
method: "POST",
|
|
12715
|
-
body: JSON.stringify({ name, comment: config.comment.slice(0, 256), is_default_network: false })
|
|
12716
|
-
}, fetcher);
|
|
12717
|
-
if (!virtualNetwork.id || !/^[0-9a-f-]{36}$/i.test(virtualNetwork.id)) {
|
|
12718
|
-
throw new Error("Cloudflare did not return the VNET id");
|
|
12719
|
-
}
|
|
12720
|
-
return { virtualNetwork, created: true };
|
|
12721
|
-
}
|
|
12722
|
-
async function ensureCloudflareWarpDevicePolicy(config, fetcher = fetch) {
|
|
12723
|
-
const name = config.name.trim();
|
|
12724
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]{0,99}$/.test(name))
|
|
12725
|
-
throw new Error("Cloudflare WARP device profile name is invalid");
|
|
12726
|
-
if (!/^[A-Za-z0-9_-]{1,128}$/.test(config.serviceTokenId))
|
|
12727
|
-
throw new Error("Cloudflare service-token id is invalid");
|
|
12728
|
-
if (!/^[0-9a-f-]{36}$/i.test(config.virtualNetworkId))
|
|
12729
|
-
throw new Error("Cloudflare VNET id is invalid");
|
|
12730
|
-
const precedence = config.precedence ?? 100;
|
|
12731
|
-
if (!Number.isInteger(precedence) || precedence < 1 || precedence > 999999) {
|
|
12732
|
-
throw new Error("Cloudflare WARP device profile precedence is invalid");
|
|
12733
|
-
}
|
|
12734
|
-
const match = `identity.service_token_uuid == "${config.serviceTokenId}"`;
|
|
12735
|
-
const listPath = `/accounts/${config.accountId}/devices/policies`;
|
|
12736
|
-
const path = `/accounts/${config.accountId}/devices/policy`;
|
|
12737
|
-
const policies = await cf(config, `${listPath}?per_page=1000`, {}, fetcher);
|
|
12738
|
-
const matches = policies.filter((policy2) => policy2.name === name);
|
|
12739
|
-
if (matches.length > 1)
|
|
12740
|
-
throw new Error(`Cloudflare WARP device profile ${name} is ambiguous`);
|
|
12741
|
-
if (matches[0]?.match && matches[0].match !== match) {
|
|
12742
|
-
throw new Error(`Cloudflare WARP device profile ${name} belongs to another enrollment identity`);
|
|
12743
|
-
}
|
|
12744
|
-
const desired = {
|
|
12745
|
-
name,
|
|
12746
|
-
match,
|
|
12747
|
-
precedence,
|
|
12748
|
-
description: "ForgeZero non-interactive compute enrollment",
|
|
12749
|
-
enabled: true,
|
|
12750
|
-
allow_mode_switch: false,
|
|
12751
|
-
allowed_to_leave: false,
|
|
12752
|
-
auto_connect: 0,
|
|
12753
|
-
switch_locked: true,
|
|
12754
|
-
service_mode_v2: { mode: "warp" },
|
|
12755
|
-
virtual_networks: { allowed: [config.virtualNetworkId], default: config.virtualNetworkId }
|
|
12756
|
-
};
|
|
12757
|
-
const policy = await cf(config, matches[0] ? `${path}/${encodeURIComponent(matches[0].id)}` : path, { method: matches[0] ? "PATCH" : "POST", body: JSON.stringify(desired) }, fetcher);
|
|
12758
|
-
if (!policy.id)
|
|
12759
|
-
throw new Error("Cloudflare did not return the WARP device profile id");
|
|
12760
|
-
return { policy, created: !matches[0] };
|
|
12761
|
-
}
|
|
12762
|
-
async function configureCloudflareWorkerAccessSecrets(config, fetcher = fetch) {
|
|
12763
|
-
if (!/^[a-z][a-z0-9-]{0,62}$/.test(config.scriptName)) {
|
|
12764
|
-
throw new Error("Cloudflare Worker script name is invalid");
|
|
12765
|
-
}
|
|
12766
|
-
await cf(config, `/accounts/${config.accountId}/workers/scripts/${config.scriptName}/secrets-bulk`, {
|
|
12767
|
-
method: "PATCH",
|
|
12768
|
-
body: JSON.stringify({
|
|
12769
|
-
secrets: {
|
|
12770
|
-
CF_ACCESS_CLIENT_ID: {
|
|
12771
|
-
name: "CF_ACCESS_CLIENT_ID",
|
|
12772
|
-
type: "secret_text",
|
|
12773
|
-
text: config.credentials.clientId
|
|
12774
|
-
},
|
|
12775
|
-
CF_ACCESS_CLIENT_SECRET: {
|
|
12776
|
-
name: "CF_ACCESS_CLIENT_SECRET",
|
|
12777
|
-
type: "secret_text",
|
|
12778
|
-
text: config.credentials.clientSecret
|
|
12779
|
-
}
|
|
12780
|
-
}
|
|
12781
|
-
})
|
|
12782
|
-
}, fetcher);
|
|
12783
|
-
}
|
|
12784
|
-
async function ensureCloudflareKvNamespace(config, fetcher = fetch) {
|
|
12785
|
-
const title = config.title.trim();
|
|
12786
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(title)) {
|
|
12787
|
-
throw new Error("Cloudflare KV namespace title is invalid");
|
|
12285
|
+
const dnsAlreadyCorrect = existingRecord?.type === record2.type && existingRecord.name?.toLowerCase() === record2.name.toLowerCase() && existingRecord.content?.toLowerCase() === record2.content.toLowerCase() && existingRecord.proxied === true && existingRecord.ttl === 1;
|
|
12286
|
+
if (!dnsAlreadyCorrect) {
|
|
12287
|
+
await cf(dnsAuth, existingRecord ? `${dnsPath}/${encodeURIComponent(existingRecord.id)}` : dnsPath, {
|
|
12288
|
+
method: existingRecord ? "PUT" : "POST",
|
|
12289
|
+
body: JSON.stringify(record2)
|
|
12290
|
+
}, fetcher);
|
|
12788
12291
|
}
|
|
12789
|
-
const path = `/accounts/${config.accountId}/storage/kv/namespaces`;
|
|
12790
|
-
const namespaces = await cf(config, `${path}?per_page=1000`, {}, fetcher);
|
|
12791
|
-
const matches = namespaces.filter((namespace2) => namespace2.title === title);
|
|
12792
|
-
if (matches.length > 1)
|
|
12793
|
-
throw new Error(`Cloudflare KV namespace ${title} is ambiguous`);
|
|
12794
|
-
if (matches[0])
|
|
12795
|
-
return { namespace: matches[0], created: false };
|
|
12796
|
-
const namespace = await cf(config, path, {
|
|
12797
|
-
method: "POST",
|
|
12798
|
-
body: JSON.stringify({ title })
|
|
12799
|
-
}, fetcher);
|
|
12800
|
-
return { namespace, created: true };
|
|
12801
12292
|
}
|
|
12802
12293
|
async function ensureCloudflareTunnel(config, fetcher = fetch) {
|
|
12803
12294
|
const name = config.name.trim();
|
|
@@ -12822,22 +12313,10 @@ async function ensureCloudflareTunnel(config, fetcher = fetch) {
|
|
|
12822
12313
|
}
|
|
12823
12314
|
|
|
12824
12315
|
// src/cloudflare-bootstrap.ts
|
|
12825
|
-
var
|
|
12826
|
-
|
|
12827
|
-
|
|
12828
|
-
|
|
12829
|
-
method: "GET",
|
|
12830
|
-
headers,
|
|
12831
|
-
redirect: "manual",
|
|
12832
|
-
signal: AbortSignal.timeout(5000)
|
|
12833
|
-
});
|
|
12834
|
-
} catch {
|
|
12835
|
-
throw new Error(`${label} is unreachable`);
|
|
12836
|
-
}
|
|
12837
|
-
if (!response.ok)
|
|
12838
|
-
throw new Error(`${label} returned HTTP ${response.status}`);
|
|
12839
|
-
return response.status;
|
|
12840
|
-
};
|
|
12316
|
+
var TOKEN = /^[A-Za-z0-9._-]{40,80}$/;
|
|
12317
|
+
var CONNECTOR_TOKEN = /^[A-Za-z0-9._-]{40,16384}$/;
|
|
12318
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
12319
|
+
var HOSTNAME = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/;
|
|
12841
12320
|
var ownerUid = () => typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
12842
12321
|
async function assertOwnerOnlyHandle(path, handle, maximumBytes) {
|
|
12843
12322
|
const metadata = await handle.stat();
|
|
@@ -12846,8 +12325,9 @@ async function assertOwnerOnlyHandle(path, handle, maximumBytes) {
|
|
|
12846
12325
|
if (metadata.nlink !== 1)
|
|
12847
12326
|
throw new Error(`${path} must not have multiple hard links`);
|
|
12848
12327
|
const uid = ownerUid();
|
|
12849
|
-
if (uid !== undefined && uid !== 0 && metadata.uid !== uid)
|
|
12328
|
+
if (uid !== undefined && uid !== 0 && metadata.uid !== uid) {
|
|
12850
12329
|
throw new Error(`${path} must be owned by the current operator`);
|
|
12330
|
+
}
|
|
12851
12331
|
if ((metadata.mode & 63) !== 0)
|
|
12852
12332
|
throw new Error(`${path} must not be accessible by group or other users`);
|
|
12853
12333
|
if ((metadata.mode & 256) === 0)
|
|
@@ -12872,25 +12352,36 @@ async function readOwnerOnlyFile(path, maximumBytes) {
|
|
|
12872
12352
|
}
|
|
12873
12353
|
async function readOwnerApiToken(path) {
|
|
12874
12354
|
const token = (await readOwnerOnlyFile(path, 4096)).trim();
|
|
12875
|
-
if (
|
|
12355
|
+
if (!TOKEN.test(token))
|
|
12876
12356
|
throw new Error(`${resolve2(path)} must contain exactly one Cloudflare API token`);
|
|
12877
|
-
}
|
|
12878
12357
|
return token;
|
|
12879
12358
|
}
|
|
12880
12359
|
async function readCloudflareBootstrapTokens(files) {
|
|
12881
12360
|
const entries = await Promise.all([
|
|
12882
12361
|
["apiToken", files.apiTokenFile],
|
|
12362
|
+
["managementApiToken", files.managementApiTokenFile],
|
|
12363
|
+
["runtimeApiToken", files.runtimeApiTokenFile],
|
|
12883
12364
|
["tunnelApiToken", files.tunnelApiTokenFile],
|
|
12884
12365
|
["dnsApiToken", files.dnsApiTokenFile],
|
|
12885
|
-
["kvApiToken", files.kvApiTokenFile]
|
|
12886
|
-
["accessApiToken", files.accessApiTokenFile],
|
|
12887
|
-
["workerApiToken", files.workerApiTokenFile]
|
|
12366
|
+
["kvApiToken", files.kvApiTokenFile]
|
|
12888
12367
|
].map(async ([key, path]) => [key, path ? await readOwnerApiToken(path) : undefined]));
|
|
12889
|
-
const
|
|
12890
|
-
const
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
12368
|
+
const supplied = Object.fromEntries(entries.filter(([, value]) => value !== undefined));
|
|
12369
|
+
const tokens = {
|
|
12370
|
+
...supplied.apiToken ? { apiToken: supplied.apiToken } : {},
|
|
12371
|
+
...supplied.tunnelApiToken || supplied.managementApiToken ? {
|
|
12372
|
+
tunnelApiToken: supplied.tunnelApiToken ?? supplied.managementApiToken
|
|
12373
|
+
} : {},
|
|
12374
|
+
...supplied.dnsApiToken || supplied.managementApiToken ? {
|
|
12375
|
+
dnsApiToken: supplied.dnsApiToken ?? supplied.managementApiToken
|
|
12376
|
+
} : {},
|
|
12377
|
+
...supplied.kvApiToken || supplied.runtimeApiToken ? {
|
|
12378
|
+
kvApiToken: supplied.kvApiToken ?? supplied.runtimeApiToken
|
|
12379
|
+
} : {}
|
|
12380
|
+
};
|
|
12381
|
+
for (const key of ["tunnelApiToken", "dnsApiToken", "kvApiToken"]) {
|
|
12382
|
+
if (!tokens[key] && !tokens.apiToken) {
|
|
12383
|
+
throw new Error(`Cloudflare ${key} file is required when apiTokenFile is omitted`);
|
|
12384
|
+
}
|
|
12894
12385
|
}
|
|
12895
12386
|
return tokens;
|
|
12896
12387
|
}
|
|
@@ -12900,36 +12391,30 @@ var validateId = (value, label) => {
|
|
|
12900
12391
|
throw new Error(`${label} must be a 32-character hexadecimal id`);
|
|
12901
12392
|
return normalized;
|
|
12902
12393
|
};
|
|
12903
|
-
var validateName = (value, label
|
|
12394
|
+
var validateName = (value, label) => {
|
|
12904
12395
|
const normalized = value.trim();
|
|
12905
|
-
|
|
12906
|
-
if (!normalized || normalized.length > maximum || !pattern.test(normalized)) {
|
|
12396
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/.test(normalized))
|
|
12907
12397
|
throw new Error(`${label} is invalid`);
|
|
12908
|
-
}
|
|
12909
12398
|
return normalized;
|
|
12910
12399
|
};
|
|
12911
|
-
var
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12915
|
-
|
|
12916
|
-
|
|
12917
|
-
|
|
12918
|
-
|
|
12919
|
-
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
return address;
|
|
12923
|
-
}
|
|
12924
|
-
throw new Error("Cloudflare private database address must be RFC 1918 IPv4 or unique-local IPv6");
|
|
12400
|
+
var normalizeService = (value) => {
|
|
12401
|
+
let service;
|
|
12402
|
+
try {
|
|
12403
|
+
service = new URL(value);
|
|
12404
|
+
} catch {
|
|
12405
|
+
throw new Error("Cloudflare Tunnel service must be an explicit loopback HTTP port");
|
|
12406
|
+
}
|
|
12407
|
+
if (service.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(service.hostname) || !service.port || service.pathname !== "/" || service.username || service.password || service.search || service.hash) {
|
|
12408
|
+
throw new Error("Cloudflare Tunnel service must be an explicit loopback HTTP port");
|
|
12409
|
+
}
|
|
12410
|
+
return service.toString().replace(/\/$/, "");
|
|
12925
12411
|
};
|
|
12926
12412
|
function validateCloudflareBootstrapCoordinates(input) {
|
|
12927
12413
|
const nodeInputs = input.nodes?.length ? input.nodes : [{
|
|
12928
12414
|
nodeName: input.tunnelName,
|
|
12929
12415
|
hostname: input.hostname,
|
|
12930
12416
|
service: input.service,
|
|
12931
|
-
tunnelName: input.tunnelName
|
|
12932
|
-
applicationName: input.applicationName
|
|
12417
|
+
tunnelName: input.tunnelName
|
|
12933
12418
|
}];
|
|
12934
12419
|
if (nodeInputs.length < 1 || nodeInputs.length > 32) {
|
|
12935
12420
|
throw new Error("Cloudflare bootstrap requires between 1 and 32 explicit nodes");
|
|
@@ -12939,88 +12424,32 @@ function validateCloudflareBootstrapCoordinates(input) {
|
|
|
12939
12424
|
if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(nodeName))
|
|
12940
12425
|
throw new Error("Cloudflare node name is invalid");
|
|
12941
12426
|
const hostname = node.hostname.trim().toLowerCase();
|
|
12942
|
-
if (
|
|
12427
|
+
if (!HOSTNAME.test(hostname))
|
|
12943
12428
|
throw new Error("Cloudflare public node hostname is invalid");
|
|
12944
|
-
}
|
|
12945
|
-
const serviceUrl = new URL(node.service);
|
|
12946
|
-
if (serviceUrl.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(serviceUrl.hostname) || !serviceUrl.port || serviceUrl.pathname !== "/" || serviceUrl.search || serviceUrl.hash) {
|
|
12947
|
-
throw new Error("Cloudflare Tunnel service must be an explicit loopback HTTP port");
|
|
12948
|
-
}
|
|
12949
12429
|
return {
|
|
12950
12430
|
nodeName,
|
|
12951
12431
|
hostname,
|
|
12952
|
-
service:
|
|
12953
|
-
tunnelName: validateName(node.tunnelName, "Cloudflare Tunnel name"
|
|
12954
|
-
applicationName: validateName(node.applicationName, "Cloudflare Access application name", 100),
|
|
12955
|
-
...node.privateAddress ? { privateAddress: privateAddress(node.privateAddress) } : {}
|
|
12432
|
+
service: normalizeService(node.service),
|
|
12433
|
+
tunnelName: validateName(node.tunnelName, "Cloudflare Tunnel name")
|
|
12956
12434
|
};
|
|
12957
12435
|
});
|
|
12958
12436
|
for (const [label, values] of [
|
|
12959
12437
|
["node name", nodes.map(({ nodeName }) => nodeName)],
|
|
12960
12438
|
["hostname", nodes.map(({ hostname }) => hostname)],
|
|
12961
|
-
["Tunnel name", nodes.map(({ tunnelName }) => tunnelName)]
|
|
12962
|
-
["Access application name", nodes.map(({ applicationName }) => applicationName)]
|
|
12439
|
+
["Tunnel name", nodes.map(({ tunnelName }) => tunnelName)]
|
|
12963
12440
|
]) {
|
|
12964
12441
|
if (new Set(values).size !== values.length)
|
|
12965
12442
|
throw new Error(`Cloudflare fleet ${label} must be unique`);
|
|
12966
12443
|
}
|
|
12967
12444
|
const first = nodes[0];
|
|
12968
|
-
const workerScriptName = input.workerScriptName.trim();
|
|
12969
|
-
if (!/^[a-z][a-z0-9-]{0,62}$/.test(workerScriptName))
|
|
12970
|
-
throw new Error("Cloudflare Worker script name is invalid");
|
|
12971
|
-
const workerCompatibilityDate = input.workerCompatibilityDate.trim();
|
|
12972
|
-
if (!/^20\d{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$/.test(workerCompatibilityDate)) {
|
|
12973
|
-
throw new Error("Cloudflare Worker compatibility date is invalid");
|
|
12974
|
-
}
|
|
12975
|
-
const publicDomains = [...new Set(input.publicDomains.map((domain) => domain.trim().toLowerCase()))];
|
|
12976
|
-
if (publicDomains.length < 1 || publicDomains.length > 10 || publicDomains.some((domain) => !/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/.test(domain) || nodes.some(({ hostname }) => hostname === domain))) {
|
|
12977
|
-
throw new Error("Cloudflare Worker public domains are invalid or include the private origin hostname");
|
|
12978
|
-
}
|
|
12979
|
-
const workerDirectory = resolve2(input.workerDirectory);
|
|
12980
|
-
const workerMain = input.workerMain.trim();
|
|
12981
|
-
if (!workerMain || workerMain.startsWith("/") || workerMain.split(/[\\/]/).includes("..")) {
|
|
12982
|
-
throw new Error("Cloudflare Worker main must be a project-relative path");
|
|
12983
|
-
}
|
|
12984
|
-
const runtimeTokenNamePrefix = input.runtimeTokenNamePrefix.trim();
|
|
12985
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(runtimeTokenNamePrefix)) {
|
|
12986
|
-
throw new Error("Cloudflare runtime-token name prefix is invalid");
|
|
12987
|
-
}
|
|
12988
|
-
if (input.createPrivateNetworkRuntimeToken && !input.createRuntimeTokens) {
|
|
12989
|
-
throw new Error("private-network runtime token requires runtime-token creation");
|
|
12990
|
-
}
|
|
12991
|
-
const privateNetwork = input.privateNetwork ? {
|
|
12992
|
-
warpOrganization: validateName(input.privateNetwork.warpOrganization, "Cloudflare WARP organization", 63, false).toLowerCase(),
|
|
12993
|
-
virtualNetworkName: validateName(input.privateNetwork.virtualNetworkName, "Cloudflare VNET name", 100),
|
|
12994
|
-
deviceProfileName: validateName(input.privateNetwork.deviceProfileName, "Cloudflare WARP device profile name", 100),
|
|
12995
|
-
enrollmentApplicationName: validateName(input.privateNetwork.enrollmentApplicationName, "Cloudflare WARP enrollment application name", 100),
|
|
12996
|
-
...input.privateNetwork.deviceProfilePrecedence !== undefined ? { deviceProfilePrecedence: input.privateNetwork.deviceProfilePrecedence } : {}
|
|
12997
|
-
} : undefined;
|
|
12998
|
-
if (privateNetwork && (!input.createPrivateNetworkRuntimeToken || nodes.every((node) => !node.privateAddress))) {
|
|
12999
|
-
throw new Error("Cloudflare private network requires its runtime token and at least one DB node private address");
|
|
13000
|
-
}
|
|
13001
|
-
if (!privateNetwork && nodes.some((node) => node.privateAddress)) {
|
|
13002
|
-
throw new Error("Cloudflare node private addresses require privateNetwork coordinates");
|
|
13003
|
-
}
|
|
13004
12445
|
return {
|
|
13005
12446
|
accountId: validateId(input.accountId, "Cloudflare account id"),
|
|
13006
12447
|
zoneId: validateId(input.zoneId, "Cloudflare zone id"),
|
|
13007
12448
|
hostname: first.hostname,
|
|
13008
12449
|
service: first.service,
|
|
13009
12450
|
tunnelName: first.tunnelName,
|
|
13010
|
-
|
|
13011
|
-
|
|
13012
|
-
serviceTokenName: validateName(input.serviceTokenName, "Cloudflare Access service-token name", 100),
|
|
13013
|
-
policyName: validateName(input.policyName, "Cloudflare Access policy name", 100),
|
|
13014
|
-
applicationName: first.applicationName,
|
|
13015
|
-
workerDirectory,
|
|
13016
|
-
workerMain,
|
|
13017
|
-
workerCompatibilityDate,
|
|
13018
|
-
publicDomains,
|
|
13019
|
-
createRuntimeTokens: input.createRuntimeTokens,
|
|
13020
|
-
createPrivateNetworkRuntimeToken: input.createPrivateNetworkRuntimeToken,
|
|
13021
|
-
runtimeTokenNamePrefix,
|
|
13022
|
-
nodes,
|
|
13023
|
-
...privateNetwork ? { privateNetwork } : {}
|
|
12451
|
+
kvNamespaceId: validateId(input.kvNamespaceId, "Cloudflare KV namespace id"),
|
|
12452
|
+
nodes
|
|
13024
12453
|
};
|
|
13025
12454
|
}
|
|
13026
12455
|
function planCloudflareBootstrap(input, outputPath) {
|
|
@@ -13032,120 +12461,18 @@ function planCloudflareBootstrap(input, outputPath) {
|
|
|
13032
12461
|
outputFile: resolve2(outputPath),
|
|
13033
12462
|
coordinates,
|
|
13034
12463
|
operations: [
|
|
13035
|
-
"create or reuse one Workers KV namespace",
|
|
13036
12464
|
"create or reuse one remotely-managed Tunnel per node and checkpoint every connector token",
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
"deploy the shared Worker once with the created NODES binding and stable custom domains",
|
|
13041
|
-
"create or reuse one shared Access service token/policy and one self-hosted application per node",
|
|
13042
|
-
...coordinates.privateNetwork ? [
|
|
13043
|
-
"create or reuse the VNET, WARP enrollment application, locked service-token device profile and exact DB host routes"
|
|
13044
|
-
] : [],
|
|
13045
|
-
"write the Access client id and secret to the existing Worker as encrypted secrets",
|
|
13046
|
-
"reconcile each node ingress rule and proxied CNAME only after all Access applications are ready"
|
|
12465
|
+
"preflight each exact DNS hostname, refuse ambiguous or incompatible records, and update its existing CNAME or create it only when absent",
|
|
12466
|
+
"reconcile each Tunnel public-hostname ingress rule to the declared loopback API service",
|
|
12467
|
+
"write one node-specific handoff containing the connector token and owner-supplied KV-write token"
|
|
13047
12468
|
],
|
|
13048
12469
|
secrets: [
|
|
13049
|
-
"API tokens are read only from owner-only files and are never
|
|
13050
|
-
"the
|
|
13051
|
-
"the normal API process
|
|
12470
|
+
"API tokens are read only from owner-only files and are never placed in argv or stdout",
|
|
12471
|
+
"the management token is never persisted; connector and KV runtime capabilities are atomically checkpointed with mode 0600",
|
|
12472
|
+
"the normal API process receives only its KV-write token and never Tunnel or DNS management authority"
|
|
13052
12473
|
]
|
|
13053
12474
|
};
|
|
13054
12475
|
}
|
|
13055
|
-
var defaultWorkerCommandRunner = async ({ command, cwd, env }) => {
|
|
13056
|
-
const child = Bun.spawn([...command], {
|
|
13057
|
-
cwd,
|
|
13058
|
-
env: { ...env },
|
|
13059
|
-
stdin: "ignore",
|
|
13060
|
-
stdout: "pipe",
|
|
13061
|
-
stderr: "pipe"
|
|
13062
|
-
});
|
|
13063
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
13064
|
-
child.exited,
|
|
13065
|
-
new Response(child.stdout).text(),
|
|
13066
|
-
new Response(child.stderr).text()
|
|
13067
|
-
]);
|
|
13068
|
-
return { exitCode, stdout, stderr };
|
|
13069
|
-
};
|
|
13070
|
-
var inheritedWorkerEnvironment = () => {
|
|
13071
|
-
const allowed = [
|
|
13072
|
-
"PATH",
|
|
13073
|
-
"HOME",
|
|
13074
|
-
"TMPDIR",
|
|
13075
|
-
"XDG_CONFIG_HOME",
|
|
13076
|
-
"XDG_CACHE_HOME",
|
|
13077
|
-
"SSL_CERT_FILE",
|
|
13078
|
-
"SSL_CERT_DIR",
|
|
13079
|
-
"NODE_EXTRA_CA_CERTS",
|
|
13080
|
-
"HTTPS_PROXY",
|
|
13081
|
-
"HTTP_PROXY",
|
|
13082
|
-
"NO_PROXY"
|
|
13083
|
-
];
|
|
13084
|
-
return Object.fromEntries(allowed.flatMap((key) => process.env[key] ? [[key, process.env[key]]] : []));
|
|
13085
|
-
};
|
|
13086
|
-
var redact = (text3, secrets) => {
|
|
13087
|
-
let safe = text3.slice(0, 4096);
|
|
13088
|
-
for (const secret of secrets)
|
|
13089
|
-
if (secret)
|
|
13090
|
-
safe = safe.split(secret).join("[REDACTED]");
|
|
13091
|
-
return safe.trim();
|
|
13092
|
-
};
|
|
13093
|
-
async function deployCloudflareWorker(coordinates, kvNamespaceId, apiToken, runner = defaultWorkerCommandRunner) {
|
|
13094
|
-
const validated = validateCloudflareBootstrapCoordinates(coordinates);
|
|
13095
|
-
const workerDirectoryMetadata = await stat(validated.workerDirectory);
|
|
13096
|
-
if (!workerDirectoryMetadata.isDirectory())
|
|
13097
|
-
throw new Error("Cloudflare Worker directory is not a directory");
|
|
13098
|
-
const workerMain = resolve2(validated.workerDirectory, validated.workerMain);
|
|
13099
|
-
const workerMainMetadata = await stat(workerMain);
|
|
13100
|
-
if (!workerMainMetadata.isFile())
|
|
13101
|
-
throw new Error("Cloudflare Worker main is not a regular file");
|
|
13102
|
-
const wrangler = resolve2(validated.workerDirectory, "node_modules/.bin/wrangler");
|
|
13103
|
-
const wranglerMetadata = await stat(wrangler);
|
|
13104
|
-
if (!wranglerMetadata.isFile())
|
|
13105
|
-
throw new Error("Cloudflare Wrangler is not installed in the Worker project");
|
|
13106
|
-
if (!/^[a-f0-9]{32}$/.test(kvNamespaceId))
|
|
13107
|
-
throw new Error("Cloudflare KV namespace id is invalid");
|
|
13108
|
-
const temporaryDirectory = await mkdtemp(join5(tmpdir(), "fz-wrangler-"));
|
|
13109
|
-
await chmod(temporaryDirectory, 448);
|
|
13110
|
-
const configurationPath = join5(temporaryDirectory, "wrangler.json");
|
|
13111
|
-
try {
|
|
13112
|
-
const handle = await open(configurationPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
|
|
13113
|
-
try {
|
|
13114
|
-
await handle.writeFile(`${JSON.stringify({
|
|
13115
|
-
name: validated.workerScriptName,
|
|
13116
|
-
main: workerMain,
|
|
13117
|
-
compatibility_date: validated.workerCompatibilityDate,
|
|
13118
|
-
workers_dev: false,
|
|
13119
|
-
routes: validated.publicDomains.map((pattern) => ({ pattern, custom_domain: true })),
|
|
13120
|
-
observability: { enabled: true },
|
|
13121
|
-
kv_namespaces: [{ binding: "NODES", id: kvNamespaceId }]
|
|
13122
|
-
}, null, 2)}
|
|
13123
|
-
`);
|
|
13124
|
-
await handle.sync();
|
|
13125
|
-
} finally {
|
|
13126
|
-
await handle.close();
|
|
13127
|
-
}
|
|
13128
|
-
const result = await runner({
|
|
13129
|
-
command: [wrangler, "deploy", "--config", configurationPath],
|
|
13130
|
-
cwd: validated.workerDirectory,
|
|
13131
|
-
env: {
|
|
13132
|
-
...inheritedWorkerEnvironment(),
|
|
13133
|
-
XDG_CONFIG_HOME: temporaryDirectory,
|
|
13134
|
-
XDG_CACHE_HOME: temporaryDirectory,
|
|
13135
|
-
WRANGLER_LOG_PATH: join5(temporaryDirectory, "wrangler.log"),
|
|
13136
|
-
CLOUDFLARE_ACCOUNT_ID: validated.accountId,
|
|
13137
|
-
CLOUDFLARE_API_TOKEN: apiToken,
|
|
13138
|
-
WRANGLER_SEND_METRICS: "false"
|
|
13139
|
-
}
|
|
13140
|
-
});
|
|
13141
|
-
if (result.exitCode !== 0) {
|
|
13142
|
-
const detail = redact(result.stderr || result.stdout || "no Wrangler diagnostic", [apiToken]);
|
|
13143
|
-
throw new Error(`Cloudflare Worker deployment failed with exit ${result.exitCode}: ${detail}`);
|
|
13144
|
-
}
|
|
13145
|
-
} finally {
|
|
13146
|
-
await rm(temporaryDirectory, { recursive: true, force: true });
|
|
13147
|
-
}
|
|
13148
|
-
}
|
|
13149
12476
|
async function readExistingOutput(path) {
|
|
13150
12477
|
try {
|
|
13151
12478
|
await lstat(path);
|
|
@@ -13154,13 +12481,15 @@ async function readExistingOutput(path) {
|
|
|
13154
12481
|
return;
|
|
13155
12482
|
throw cause;
|
|
13156
12483
|
}
|
|
13157
|
-
|
|
13158
|
-
let output;
|
|
12484
|
+
let parsed;
|
|
13159
12485
|
try {
|
|
13160
|
-
|
|
13161
|
-
} catch {
|
|
13162
|
-
|
|
12486
|
+
parsed = JSON.parse(await readOwnerOnlyFile(path, 1048576));
|
|
12487
|
+
} catch (cause) {
|
|
12488
|
+
if (cause instanceof SyntaxError)
|
|
12489
|
+
throw new Error(`${resolve2(path)} is not valid bootstrap JSON`);
|
|
12490
|
+
throw cause;
|
|
13163
12491
|
}
|
|
12492
|
+
const output = parsed;
|
|
13164
12493
|
if (output.format !== 1 || output.kind !== "forgezero-cloudflare-bootstrap" || !output.resources) {
|
|
13165
12494
|
throw new Error(`${resolve2(path)} is not a ForgeZero Cloudflare bootstrap output`);
|
|
13166
12495
|
}
|
|
@@ -13201,44 +12530,29 @@ async function readCloudflareHostHandoff(handoffPath, nodeName) {
|
|
|
13201
12530
|
].includes(key));
|
|
13202
12531
|
if (unknown.length)
|
|
13203
12532
|
throw new Error(`Cloudflare host handoff contains unsupported field ${unknown[0]}`);
|
|
13204
|
-
if (output.warp) {
|
|
13205
|
-
const unknownWarp = Object.keys(output.warp).filter((key) => ![
|
|
13206
|
-
"organization",
|
|
13207
|
-
"clientId",
|
|
13208
|
-
"clientSecret",
|
|
13209
|
-
"virtualNetworkId",
|
|
13210
|
-
"deviceProfileId"
|
|
13211
|
-
].includes(key));
|
|
13212
|
-
if (unknownWarp.length)
|
|
13213
|
-
throw new Error(`Cloudflare host handoff WARP contains unsupported field ${unknownWarp[0]}`);
|
|
13214
|
-
}
|
|
13215
12533
|
const normalizedNodeName = nodeName.trim().toLowerCase();
|
|
13216
12534
|
let service;
|
|
13217
12535
|
try {
|
|
13218
|
-
service =
|
|
12536
|
+
service = normalizeService(output.service ?? "");
|
|
13219
12537
|
} catch {}
|
|
13220
|
-
if (output.format !== 1 || output.kind !== "forgezero-cloudflare-host-handoff" || output.nodeName !== normalizedNodeName ||
|
|
12538
|
+
if (output.format !== 1 || output.kind !== "forgezero-cloudflare-host-handoff" || output.nodeName !== normalizedNodeName || !HOSTNAME.test(output.hostname ?? "") || !service || !UUID.test(output.tunnelId ?? "") || !CONNECTOR_TOKEN.test(output.connectorToken ?? "") || !TOKEN.test(output.kvRuntimeToken ?? "") || !/^[a-f0-9]{32}$/i.test(output.accountId ?? "") || !/^[a-f0-9]{32}$/i.test(output.zoneId ?? "") || !/^[a-f0-9]{32}$/i.test(output.kvNamespaceId ?? "")) {
|
|
13221
12539
|
throw new Error("Cloudflare host handoff is malformed or belongs to another node");
|
|
13222
12540
|
}
|
|
13223
|
-
|
|
13224
|
-
if (network !== undefined && !/^[A-Za-z0-9._-]{40,80}$/.test(network)) {
|
|
12541
|
+
if (output.privateNetworkRuntimeToken !== undefined && !TOKEN.test(output.privateNetworkRuntimeToken)) {
|
|
13225
12542
|
throw new Error("Cloudflare host handoff private-network capability is malformed");
|
|
13226
12543
|
}
|
|
13227
|
-
if (Boolean(output.warp) !== Boolean(
|
|
12544
|
+
if (Boolean(output.warp) !== Boolean(output.privateNetworkRuntimeToken)) {
|
|
13228
12545
|
throw new Error("Cloudflare host handoff private-network resources and capability disagree");
|
|
13229
12546
|
}
|
|
13230
|
-
if (output.warp && (!output.warp.clientId || !output.warp.clientSecret || !/^[A-Za-z0-9][A-Za-z0-9-]{0,62}$/.test(output.warp.organization) || !/^[0-9a-f-]{36}$/i.test(output.warp.virtualNetworkId) || !output.warp.deviceProfileId)) {
|
|
13231
|
-
throw new Error("Cloudflare host handoff WARP enrollment is malformed");
|
|
13232
|
-
}
|
|
13233
12547
|
const { format: _format, kind: _kind, ...handoff } = output;
|
|
13234
12548
|
return handoff;
|
|
13235
12549
|
}
|
|
13236
12550
|
async function prepareOwnerOutputDirectory(absolutePath) {
|
|
13237
12551
|
const directory = dirname3(absolutePath);
|
|
13238
12552
|
await mkdir(directory, { recursive: true, mode: 448 });
|
|
13239
|
-
const
|
|
12553
|
+
const metadata = await stat(directory);
|
|
13240
12554
|
const uid = ownerUid();
|
|
13241
|
-
if (!
|
|
12555
|
+
if (!metadata.isDirectory() || uid !== undefined && metadata.uid !== uid || (metadata.mode & 18) !== 0) {
|
|
13242
12556
|
throw new Error(`bootstrap output directory ${directory} must be operator-owned and not group/other writable`);
|
|
13243
12557
|
}
|
|
13244
12558
|
return directory;
|
|
@@ -13275,13 +12589,6 @@ async function writeOwnerBootstrapOutput(path, output) {
|
|
|
13275
12589
|
await writeOwnerJson(path, output);
|
|
13276
12590
|
}
|
|
13277
12591
|
async function writeCloudflareHostHandoffs(checkpointPath, output) {
|
|
13278
|
-
const kv = output.resources.runtimeTokens?.kv?.value;
|
|
13279
|
-
if (!kv || !/^[A-Za-z0-9._-]{40,80}$/.test(kv)) {
|
|
13280
|
-
return;
|
|
13281
|
-
}
|
|
13282
|
-
const network = output.resources.runtimeTokens?.privateNetwork?.value;
|
|
13283
|
-
const privateNetwork = output.resources.privateNetwork;
|
|
13284
|
-
const access = output.resources.access;
|
|
13285
12592
|
for (const node of output.resources.nodes) {
|
|
13286
12593
|
const handoff = {
|
|
13287
12594
|
format: 1,
|
|
@@ -13294,34 +12601,19 @@ async function writeCloudflareHostHandoffs(checkpointPath, output) {
|
|
|
13294
12601
|
accountId: output.coordinates.accountId,
|
|
13295
12602
|
zoneId: output.coordinates.zoneId,
|
|
13296
12603
|
kvNamespaceId: output.resources.kvNamespaceId,
|
|
13297
|
-
kvRuntimeToken:
|
|
13298
|
-
...network ? { privateNetworkRuntimeToken: network } : {},
|
|
13299
|
-
...privateNetwork && access ? { warp: {
|
|
13300
|
-
organization: privateNetwork.warpOrganization,
|
|
13301
|
-
clientId: access.clientId,
|
|
13302
|
-
clientSecret: access.clientSecret,
|
|
13303
|
-
virtualNetworkId: privateNetwork.virtualNetworkId,
|
|
13304
|
-
deviceProfileId: privateNetwork.deviceProfileId
|
|
13305
|
-
} } : {}
|
|
12604
|
+
kvRuntimeToken: output.resources.kvRuntimeToken
|
|
13306
12605
|
};
|
|
13307
12606
|
await writeOwnerJson(cloudflareHostHandoffPath(checkpointPath, node.nodeName), handoff);
|
|
13308
12607
|
}
|
|
13309
12608
|
}
|
|
13310
12609
|
var tokenFor = (tokens, key) => {
|
|
13311
12610
|
const token = tokens[key]?.trim() || tokens.apiToken?.trim();
|
|
13312
|
-
if (!token)
|
|
12611
|
+
if (!token || !TOKEN.test(token))
|
|
13313
12612
|
throw new Error(`Cloudflare ${key} is not configured`);
|
|
13314
12613
|
return token;
|
|
13315
12614
|
};
|
|
13316
|
-
var initialManagementToken = (tokens) => {
|
|
13317
|
-
const token = tokens.apiToken?.trim();
|
|
13318
|
-
if (!token) {
|
|
13319
|
-
throw new Error("initial Cloudflare --token-file is required to create account-owned runtime tokens");
|
|
13320
|
-
}
|
|
13321
|
-
return token;
|
|
13322
|
-
};
|
|
13323
12615
|
var sameCoordinates = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
13324
|
-
async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fetch
|
|
12616
|
+
async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fetch) {
|
|
13325
12617
|
const coordinates = validateCloudflareBootstrapCoordinates(input);
|
|
13326
12618
|
const absoluteOutput = resolve2(outputPath);
|
|
13327
12619
|
const existing = await readExistingOutput(absoluteOutput);
|
|
@@ -13329,19 +12621,18 @@ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fet
|
|
|
13329
12621
|
throw new Error("bootstrap output belongs to different Cloudflare coordinates; choose a different output file");
|
|
13330
12622
|
}
|
|
13331
12623
|
await prepareOwnerOutputDirectory(absoluteOutput);
|
|
13332
|
-
const
|
|
13333
|
-
accountId: coordinates.accountId,
|
|
13334
|
-
title: coordinates.kvNamespaceTitle,
|
|
13335
|
-
apiToken: tokenFor(tokens, "kvApiToken")
|
|
13336
|
-
}, fetcher);
|
|
12624
|
+
const kvRuntimeToken = tokenFor(tokens, "kvApiToken");
|
|
13337
12625
|
const nodeResources = [];
|
|
13338
12626
|
const createdNodes = [];
|
|
13339
|
-
let resources;
|
|
13340
12627
|
for (const node of coordinates.nodes) {
|
|
13341
12628
|
const checkpointed = existing?.resources.nodes?.find(({ nodeName }) => nodeName === node.nodeName);
|
|
12629
|
+
let resource;
|
|
13342
12630
|
let created = false;
|
|
13343
12631
|
if (checkpointed) {
|
|
13344
|
-
|
|
12632
|
+
if (checkpointed.hostname !== node.hostname || checkpointed.service !== node.service || checkpointed.tunnelName !== node.tunnelName || !UUID.test(checkpointed.tunnelId) || !CONNECTOR_TOKEN.test(checkpointed.connectorToken)) {
|
|
12633
|
+
throw new Error(`checkpointed Cloudflare node ${node.nodeName} is malformed`);
|
|
12634
|
+
}
|
|
12635
|
+
resource = checkpointed;
|
|
13345
12636
|
} else {
|
|
13346
12637
|
const tunnel = await ensureCloudflareTunnel({
|
|
13347
12638
|
accountId: coordinates.accountId,
|
|
@@ -13349,234 +12640,29 @@ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fet
|
|
|
13349
12640
|
apiToken: tokenFor(tokens, "tunnelApiToken")
|
|
13350
12641
|
}, fetcher);
|
|
13351
12642
|
created = tunnel.created;
|
|
13352
|
-
|
|
13353
|
-
nodeName: node.nodeName,
|
|
13354
|
-
hostname: node.hostname,
|
|
13355
|
-
service: node.service,
|
|
13356
|
-
tunnelName: node.tunnelName,
|
|
13357
|
-
tunnelId: tunnel.tunnel.id,
|
|
13358
|
-
connectorToken: tunnel.connectorToken
|
|
13359
|
-
});
|
|
12643
|
+
resource = { ...node, tunnelId: tunnel.tunnel.id, connectorToken: tunnel.connectorToken };
|
|
13360
12644
|
}
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
tunnelId: firstNode.tunnelId,
|
|
13365
|
-
kvNamespaceId: namespace.namespace.id,
|
|
13366
|
-
hostname: firstNode.hostname,
|
|
13367
|
-
service: firstNode.service,
|
|
13368
|
-
connectorToken: firstNode.connectorToken,
|
|
13369
|
-
nodes: [...nodeResources],
|
|
13370
|
-
...existing?.resources.access ? { access: existing.resources.access } : {},
|
|
13371
|
-
...existing?.resources.runtimeTokens ? { runtimeTokens: existing.resources.runtimeTokens } : {},
|
|
13372
|
-
...existing?.resources.worker ? { worker: existing.resources.worker } : {},
|
|
13373
|
-
...existing?.resources.privateNetwork ? { privateNetwork: existing.resources.privateNetwork } : {}
|
|
13374
|
-
};
|
|
13375
|
-
await writeOwnerBootstrapOutput(absoluteOutput, {
|
|
13376
|
-
format: 1,
|
|
13377
|
-
kind: "forgezero-cloudflare-bootstrap",
|
|
13378
|
-
phase: resources.access ? "access-token-provisioned" : "edge-resources-provisioned",
|
|
13379
|
-
updatedAt: new Date().toISOString(),
|
|
13380
|
-
coordinates,
|
|
13381
|
-
resources
|
|
13382
|
-
});
|
|
13383
|
-
}
|
|
13384
|
-
if (!resources)
|
|
13385
|
-
throw new Error("Cloudflare fleet has no nodes");
|
|
13386
|
-
if (coordinates.createRuntimeTokens && !resources.runtimeTokens) {
|
|
13387
|
-
resources = {
|
|
13388
|
-
...resources,
|
|
13389
|
-
runtimeTokens: { kv: await createCloudflareAccountRuntimeToken({
|
|
13390
|
-
accountId: coordinates.accountId,
|
|
13391
|
-
name: `${coordinates.runtimeTokenNamePrefix}-kv-runtime`,
|
|
13392
|
-
permissionNames: ["Workers KV Storage Write"],
|
|
13393
|
-
apiToken: initialManagementToken(tokens)
|
|
13394
|
-
}, fetcher) }
|
|
13395
|
-
};
|
|
13396
|
-
await writeOwnerBootstrapOutput(absoluteOutput, {
|
|
13397
|
-
format: 1,
|
|
13398
|
-
kind: "forgezero-cloudflare-bootstrap",
|
|
13399
|
-
phase: "runtime-tokens-created",
|
|
13400
|
-
updatedAt: new Date().toISOString(),
|
|
13401
|
-
coordinates,
|
|
13402
|
-
resources
|
|
13403
|
-
});
|
|
13404
|
-
}
|
|
13405
|
-
if (coordinates.createPrivateNetworkRuntimeToken && resources.runtimeTokens && !resources.runtimeTokens.privateNetwork) {
|
|
13406
|
-
resources = {
|
|
13407
|
-
...resources,
|
|
13408
|
-
runtimeTokens: {
|
|
13409
|
-
...resources.runtimeTokens,
|
|
13410
|
-
privateNetwork: await createCloudflareAccountRuntimeToken({
|
|
13411
|
-
accountId: coordinates.accountId,
|
|
13412
|
-
name: `${coordinates.runtimeTokenNamePrefix}-private-network-runtime`,
|
|
13413
|
-
permissionNames: ["Cloudflare One Networks Write", "Zero Trust Write"],
|
|
13414
|
-
apiToken: initialManagementToken(tokens)
|
|
13415
|
-
}, fetcher)
|
|
13416
|
-
}
|
|
13417
|
-
};
|
|
12645
|
+
nodeResources.push(resource);
|
|
12646
|
+
createdNodes.push({ nodeName: node.nodeName, tunnel: created });
|
|
12647
|
+
const first2 = nodeResources[0];
|
|
13418
12648
|
await writeOwnerBootstrapOutput(absoluteOutput, {
|
|
13419
12649
|
format: 1,
|
|
13420
12650
|
kind: "forgezero-cloudflare-bootstrap",
|
|
13421
|
-
phase: "
|
|
12651
|
+
phase: "edge-resources-provisioned",
|
|
13422
12652
|
updatedAt: new Date().toISOString(),
|
|
13423
12653
|
coordinates,
|
|
13424
|
-
resources
|
|
13425
|
-
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
|
|
13432
|
-
scriptName: coordinates.workerScriptName,
|
|
13433
|
-
publicDomains: coordinates.publicDomains,
|
|
13434
|
-
deployed: true
|
|
12654
|
+
resources: {
|
|
12655
|
+
tunnelId: first2.tunnelId,
|
|
12656
|
+
kvNamespaceId: coordinates.kvNamespaceId,
|
|
12657
|
+
hostname: first2.hostname,
|
|
12658
|
+
service: first2.service,
|
|
12659
|
+
connectorToken: first2.connectorToken,
|
|
12660
|
+
kvRuntimeToken,
|
|
12661
|
+
nodes: [...nodeResources]
|
|
13435
12662
|
}
|
|
13436
|
-
};
|
|
13437
|
-
await writeOwnerBootstrapOutput(absoluteOutput, {
|
|
13438
|
-
format: 1,
|
|
13439
|
-
kind: "forgezero-cloudflare-bootstrap",
|
|
13440
|
-
phase: "worker-deployed",
|
|
13441
|
-
updatedAt: new Date().toISOString(),
|
|
13442
|
-
coordinates,
|
|
13443
|
-
resources
|
|
13444
12663
|
});
|
|
13445
12664
|
}
|
|
13446
|
-
const
|
|
13447
|
-
accountId: coordinates.accountId,
|
|
13448
|
-
name: coordinates.serviceTokenName,
|
|
13449
|
-
apiToken: tokenFor(tokens, "accessApiToken"),
|
|
13450
|
-
existing: resources.access
|
|
13451
|
-
}, fetcher);
|
|
13452
|
-
resources = { ...resources, access: serviceToken.credentials };
|
|
13453
|
-
await writeOwnerBootstrapOutput(absoluteOutput, {
|
|
13454
|
-
format: 1,
|
|
13455
|
-
kind: "forgezero-cloudflare-bootstrap",
|
|
13456
|
-
phase: "access-token-provisioned",
|
|
13457
|
-
updatedAt: new Date().toISOString(),
|
|
13458
|
-
coordinates,
|
|
13459
|
-
resources
|
|
13460
|
-
});
|
|
13461
|
-
const policy = await ensureCloudflareAccessPolicy({
|
|
13462
|
-
accountId: coordinates.accountId,
|
|
13463
|
-
name: coordinates.policyName,
|
|
13464
|
-
serviceTokenId: serviceToken.credentials.tokenId,
|
|
13465
|
-
apiToken: tokenFor(tokens, "accessApiToken")
|
|
13466
|
-
}, fetcher);
|
|
13467
|
-
if (!policy.policy.id)
|
|
13468
|
-
throw new Error("Cloudflare did not return the Access policy id");
|
|
13469
|
-
resources = {
|
|
13470
|
-
...resources,
|
|
13471
|
-
access: { ...serviceToken.credentials, policyId: policy.policy.id }
|
|
13472
|
-
};
|
|
13473
|
-
await writeOwnerBootstrapOutput(absoluteOutput, {
|
|
13474
|
-
format: 1,
|
|
13475
|
-
kind: "forgezero-cloudflare-bootstrap",
|
|
13476
|
-
phase: "access-token-provisioned",
|
|
13477
|
-
updatedAt: new Date().toISOString(),
|
|
13478
|
-
coordinates,
|
|
13479
|
-
resources
|
|
13480
|
-
});
|
|
13481
|
-
let privateNetworkCreated = false;
|
|
13482
|
-
if (coordinates.privateNetwork && !resources.privateNetwork) {
|
|
13483
|
-
const managementToken = initialManagementToken(tokens);
|
|
13484
|
-
const virtualNetwork = await ensureCloudflareVirtualNetwork({
|
|
13485
|
-
accountId: coordinates.accountId,
|
|
13486
|
-
name: coordinates.privateNetwork.virtualNetworkName,
|
|
13487
|
-
comment: "ForgeZero private database network",
|
|
13488
|
-
apiToken: managementToken
|
|
13489
|
-
}, fetcher);
|
|
13490
|
-
const enrollment = await ensureCloudflareWarpEnrollmentApplication({
|
|
13491
|
-
accountId: coordinates.accountId,
|
|
13492
|
-
name: coordinates.privateNetwork.enrollmentApplicationName,
|
|
13493
|
-
policyId: policy.policy.id,
|
|
13494
|
-
apiToken: tokenFor(tokens, "accessApiToken")
|
|
13495
|
-
}, fetcher);
|
|
13496
|
-
const deviceProfile = await ensureCloudflareWarpDevicePolicy({
|
|
13497
|
-
accountId: coordinates.accountId,
|
|
13498
|
-
name: coordinates.privateNetwork.deviceProfileName,
|
|
13499
|
-
serviceTokenId: serviceToken.credentials.tokenId,
|
|
13500
|
-
virtualNetworkId: virtualNetwork.virtualNetwork.id,
|
|
13501
|
-
precedence: coordinates.privateNetwork.deviceProfilePrecedence,
|
|
13502
|
-
apiToken: managementToken
|
|
13503
|
-
}, fetcher);
|
|
13504
|
-
const routes = [];
|
|
13505
|
-
for (const node of coordinates.nodes.filter((item) => item.privateAddress)) {
|
|
13506
|
-
const resource = resources.nodes.find((item) => item.nodeName === node.nodeName);
|
|
13507
|
-
const route = await ensureCloudflarePrivateDatabaseRoute({
|
|
13508
|
-
accountId: coordinates.accountId,
|
|
13509
|
-
tunnelId: resource.tunnelId,
|
|
13510
|
-
privateAddress: node.privateAddress,
|
|
13511
|
-
virtualNetworkId: virtualNetwork.virtualNetwork.id,
|
|
13512
|
-
comment: `ForgeZero ${node.nodeName} database`,
|
|
13513
|
-
apiToken: managementToken
|
|
13514
|
-
}, fetcher);
|
|
13515
|
-
await ensureCloudflareWarpDatabaseInclude({
|
|
13516
|
-
accountId: coordinates.accountId,
|
|
13517
|
-
policyId: deviceProfile.policy.id,
|
|
13518
|
-
privateAddress: node.privateAddress,
|
|
13519
|
-
description: `ForgeZero ${node.nodeName} database`,
|
|
13520
|
-
apiToken: managementToken
|
|
13521
|
-
}, fetcher);
|
|
13522
|
-
routes.push({ nodeName: node.nodeName, routeId: route.route.id, privateAddress: node.privateAddress });
|
|
13523
|
-
}
|
|
13524
|
-
resources = {
|
|
13525
|
-
...resources,
|
|
13526
|
-
privateNetwork: {
|
|
13527
|
-
warpOrganization: coordinates.privateNetwork.warpOrganization,
|
|
13528
|
-
virtualNetworkId: virtualNetwork.virtualNetwork.id,
|
|
13529
|
-
deviceProfileId: deviceProfile.policy.id,
|
|
13530
|
-
enrollmentApplicationId: enrollment.application.id,
|
|
13531
|
-
routes
|
|
13532
|
-
}
|
|
13533
|
-
};
|
|
13534
|
-
privateNetworkCreated = virtualNetwork.created || enrollment.created || deviceProfile.created || routes.length > 0;
|
|
13535
|
-
await writeOwnerBootstrapOutput(absoluteOutput, {
|
|
13536
|
-
format: 1,
|
|
13537
|
-
kind: "forgezero-cloudflare-bootstrap",
|
|
13538
|
-
phase: "access-token-provisioned",
|
|
13539
|
-
updatedAt: new Date().toISOString(),
|
|
13540
|
-
coordinates,
|
|
13541
|
-
resources
|
|
13542
|
-
});
|
|
13543
|
-
}
|
|
13544
|
-
for (const node of coordinates.nodes) {
|
|
13545
|
-
const application = await ensureCloudflareAccessApplication({
|
|
13546
|
-
accountId: coordinates.accountId,
|
|
13547
|
-
name: node.applicationName,
|
|
13548
|
-
hostname: node.hostname,
|
|
13549
|
-
policyId: policy.policy.id,
|
|
13550
|
-
apiToken: tokenFor(tokens, "accessApiToken")
|
|
13551
|
-
}, fetcher);
|
|
13552
|
-
if (!application.application.id)
|
|
13553
|
-
throw new Error(`Cloudflare did not return the Access application id for ${node.nodeName}`);
|
|
13554
|
-
resources = {
|
|
13555
|
-
...resources,
|
|
13556
|
-
nodes: resources.nodes.map((resource) => resource.nodeName === node.nodeName ? { ...resource, applicationId: application.application.id } : resource),
|
|
13557
|
-
access: {
|
|
13558
|
-
...resources.access,
|
|
13559
|
-
...node.nodeName === coordinates.nodes[0].nodeName ? { applicationId: application.application.id } : {}
|
|
13560
|
-
}
|
|
13561
|
-
};
|
|
13562
|
-
const createdNode = createdNodes.find(({ nodeName }) => nodeName === node.nodeName);
|
|
13563
|
-
createdNode.application = application.created;
|
|
13564
|
-
await writeOwnerBootstrapOutput(absoluteOutput, {
|
|
13565
|
-
format: 1,
|
|
13566
|
-
kind: "forgezero-cloudflare-bootstrap",
|
|
13567
|
-
phase: "access-token-provisioned",
|
|
13568
|
-
updatedAt: new Date().toISOString(),
|
|
13569
|
-
coordinates,
|
|
13570
|
-
resources
|
|
13571
|
-
});
|
|
13572
|
-
}
|
|
13573
|
-
await configureCloudflareWorkerAccessSecrets({
|
|
13574
|
-
accountId: coordinates.accountId,
|
|
13575
|
-
scriptName: coordinates.workerScriptName,
|
|
13576
|
-
credentials: serviceToken.credentials,
|
|
13577
|
-
apiToken: tokenFor(tokens, "workerApiToken")
|
|
13578
|
-
}, fetcher);
|
|
13579
|
-
for (const node of resources.nodes) {
|
|
12665
|
+
for (const node of nodeResources) {
|
|
13580
12666
|
await configureCloudflareEdge({
|
|
13581
12667
|
accountId: coordinates.accountId,
|
|
13582
12668
|
zoneId: coordinates.zoneId,
|
|
@@ -13588,21 +12674,24 @@ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fet
|
|
|
13588
12674
|
dnsApiToken: tokenFor(tokens, "dnsApiToken")
|
|
13589
12675
|
}, fetcher);
|
|
13590
12676
|
}
|
|
12677
|
+
const first = nodeResources[0];
|
|
13591
12678
|
const output = {
|
|
13592
12679
|
format: 1,
|
|
13593
12680
|
kind: "forgezero-cloudflare-bootstrap",
|
|
13594
12681
|
phase: "complete",
|
|
13595
12682
|
updatedAt: new Date().toISOString(),
|
|
13596
12683
|
coordinates,
|
|
13597
|
-
resources
|
|
12684
|
+
resources: {
|
|
12685
|
+
tunnelId: first.tunnelId,
|
|
12686
|
+
kvNamespaceId: coordinates.kvNamespaceId,
|
|
12687
|
+
hostname: first.hostname,
|
|
12688
|
+
service: first.service,
|
|
12689
|
+
connectorToken: first.connectorToken,
|
|
12690
|
+
kvRuntimeToken,
|
|
12691
|
+
nodes: nodeResources
|
|
12692
|
+
},
|
|
13598
12693
|
created: {
|
|
13599
12694
|
tunnel: createdNodes.some(({ tunnel }) => tunnel),
|
|
13600
|
-
kvNamespace: namespace.created,
|
|
13601
|
-
serviceToken: serviceToken.created,
|
|
13602
|
-
policy: policy.created,
|
|
13603
|
-
application: createdNodes.some(({ application }) => application),
|
|
13604
|
-
privateNetwork: privateNetworkCreated,
|
|
13605
|
-
workerDeployed: true,
|
|
13606
12695
|
nodes: createdNodes
|
|
13607
12696
|
}
|
|
13608
12697
|
};
|
|
@@ -13610,99 +12699,73 @@ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fet
|
|
|
13610
12699
|
await writeCloudflareHostHandoffs(absoluteOutput, output);
|
|
13611
12700
|
return output;
|
|
13612
12701
|
}
|
|
13613
|
-
async function runAttendedCloudflareBootstrap(
|
|
13614
|
-
const plan = planCloudflareBootstrap(
|
|
13615
|
-
if (
|
|
12702
|
+
async function runAttendedCloudflareBootstrap(request, dependencies = {}) {
|
|
12703
|
+
const plan = planCloudflareBootstrap(request.coordinates, request.checkpointPath);
|
|
12704
|
+
if (request.mode === "plan") {
|
|
13616
12705
|
return {
|
|
13617
12706
|
format: 1,
|
|
13618
12707
|
kind: "forgezero-cloudflare-bootstrap-evidence",
|
|
13619
12708
|
phase: "planned",
|
|
13620
12709
|
checkpointFile: plan.outputFile,
|
|
13621
|
-
workerScriptName: plan.coordinates.workerScriptName,
|
|
13622
|
-
publicDomains: plan.coordinates.publicDomains,
|
|
13623
12710
|
nodes: plan.coordinates.nodes.map(({ nodeName, hostname }) => ({ nodeName, hostname }))
|
|
13624
12711
|
};
|
|
13625
12712
|
}
|
|
13626
|
-
if (!
|
|
12713
|
+
if (!request.tokenFiles || !Object.values(request.tokenFiles).some(Boolean)) {
|
|
13627
12714
|
throw new Error("Cloudflare apply requires owner-only management token file paths");
|
|
13628
12715
|
}
|
|
13629
|
-
|
|
13630
|
-
|
|
13631
|
-
}
|
|
13632
|
-
const tokens = await readCloudflareBootstrapTokens(request2.tokenFiles);
|
|
13633
|
-
const output = await applyCloudflareBootstrap(plan.coordinates, tokens, plan.outputFile, dependencies.fetcher ?? fetch, dependencies.workerRunner);
|
|
12716
|
+
const tokens = await readCloudflareBootstrapTokens(request.tokenFiles);
|
|
12717
|
+
const output = await applyCloudflareBootstrap(plan.coordinates, tokens, plan.outputFile, dependencies.fetcher ?? fetch);
|
|
13634
12718
|
return {
|
|
13635
12719
|
format: 1,
|
|
13636
12720
|
kind: "forgezero-cloudflare-bootstrap-evidence",
|
|
13637
12721
|
phase: "complete",
|
|
13638
12722
|
checkpointFile: plan.outputFile,
|
|
13639
12723
|
kvNamespaceId: output.resources.kvNamespaceId,
|
|
13640
|
-
|
|
13641
|
-
publicDomains: output.resources.worker?.publicDomains ?? output.coordinates.publicDomains,
|
|
13642
|
-
runtimeTokenIds: {
|
|
13643
|
-
kv: output.resources.runtimeTokens?.kv.id,
|
|
13644
|
-
privateNetwork: output.resources.runtimeTokens?.privateNetwork?.id
|
|
13645
|
-
},
|
|
13646
|
-
nodes: output.resources.nodes.map(({ nodeName, hostname, tunnelId, applicationId }) => ({
|
|
12724
|
+
nodes: output.resources.nodes.map(({ nodeName, hostname, tunnelId }) => ({
|
|
13647
12725
|
nodeName,
|
|
13648
12726
|
hostname,
|
|
13649
|
-
|
|
13650
|
-
|
|
13651
|
-
} : {},
|
|
13652
|
-
tunnelId,
|
|
13653
|
-
applicationId
|
|
12727
|
+
handoffFile: cloudflareHostHandoffPath(plan.outputFile, nodeName),
|
|
12728
|
+
tunnelId
|
|
13654
12729
|
}))
|
|
13655
12730
|
};
|
|
13656
12731
|
}
|
|
12732
|
+
var acceptanceFetch = async (url, label, fetcher) => {
|
|
12733
|
+
let response;
|
|
12734
|
+
try {
|
|
12735
|
+
response = await fetcher(url, { method: "GET", redirect: "manual", signal: AbortSignal.timeout(5000) });
|
|
12736
|
+
} catch {
|
|
12737
|
+
throw new Error(`${label} is unreachable`);
|
|
12738
|
+
}
|
|
12739
|
+
if (!response.ok)
|
|
12740
|
+
throw new Error(`${label} returned HTTP ${response.status}`);
|
|
12741
|
+
return response.status;
|
|
12742
|
+
};
|
|
13657
12743
|
async function verifyCloudflareBootstrapAcceptance(checkpointPath, fetcher = fetch) {
|
|
13658
12744
|
const absolute = resolve2(checkpointPath);
|
|
13659
12745
|
const output = await readExistingOutput(absolute);
|
|
13660
12746
|
if (!output || output.phase !== "complete")
|
|
13661
12747
|
throw new Error("Cloudflare acceptance requires a completed owner checkpoint");
|
|
13662
12748
|
const coordinates = validateCloudflareBootstrapCoordinates(output.coordinates);
|
|
13663
|
-
const access = output.resources.access;
|
|
13664
|
-
if (!access?.clientId?.trim() || !access.clientSecret?.trim()) {
|
|
13665
|
-
throw new Error("Cloudflare acceptance checkpoint is missing the Access service credential");
|
|
13666
|
-
}
|
|
13667
|
-
if (!output.resources.worker?.deployed || output.resources.worker.scriptName !== coordinates.workerScriptName || JSON.stringify(output.resources.worker.publicDomains) !== JSON.stringify(coordinates.publicDomains)) {
|
|
13668
|
-
throw new Error("Cloudflare acceptance checkpoint does not prove the expected Worker deployment");
|
|
13669
|
-
}
|
|
13670
12749
|
if (output.resources.nodes.length !== coordinates.nodes.length) {
|
|
13671
12750
|
throw new Error("Cloudflare acceptance checkpoint does not cover the declared node fleet");
|
|
13672
12751
|
}
|
|
13673
|
-
const nodeNames = new Set;
|
|
13674
|
-
const hostnames = new Set;
|
|
13675
12752
|
for (const node of output.resources.nodes) {
|
|
13676
12753
|
const expected = coordinates.nodes.find((candidate) => candidate.nodeName === node.nodeName);
|
|
13677
|
-
if (!expected || node.hostname !== expected.hostname || node.service !== expected.service || node.tunnelName !== expected.tunnelName ||
|
|
12754
|
+
if (!expected || node.hostname !== expected.hostname || node.service !== expected.service || node.tunnelName !== expected.tunnelName || !UUID.test(node.tunnelId)) {
|
|
13678
12755
|
throw new Error("Cloudflare acceptance checkpoint has an unbound node resource");
|
|
13679
12756
|
}
|
|
13680
|
-
if (nodeNames.has(node.nodeName) || hostnames.has(node.hostname)) {
|
|
13681
|
-
throw new Error("Cloudflare acceptance checkpoint has duplicate node coordinates");
|
|
13682
|
-
}
|
|
13683
|
-
nodeNames.add(node.nodeName);
|
|
13684
|
-
hostnames.add(node.hostname);
|
|
13685
12757
|
}
|
|
13686
|
-
const accessHeaders = {
|
|
13687
|
-
"CF-Access-Client-Id": access.clientId,
|
|
13688
|
-
"CF-Access-Client-Secret": access.clientSecret
|
|
13689
|
-
};
|
|
13690
12758
|
const nodes = await Promise.all(output.resources.nodes.map(async ({ nodeName, hostname }) => ({
|
|
13691
12759
|
nodeName,
|
|
13692
12760
|
hostname,
|
|
13693
|
-
status: await acceptanceFetch(`https://${hostname}/api/health`, `Cloudflare origin ${nodeName}`, fetcher
|
|
13694
|
-
})));
|
|
13695
|
-
const publicDomains = await Promise.all(output.resources.worker.publicDomains.map(async (hostname) => ({
|
|
13696
|
-
hostname,
|
|
13697
|
-
status: await acceptanceFetch(`https://${hostname}/api/health`, `Cloudflare public domain ${hostname}`, fetcher)
|
|
12761
|
+
status: await acceptanceFetch(`https://${hostname}/api/health`, `Cloudflare origin ${nodeName}`, fetcher)
|
|
13698
12762
|
})));
|
|
13699
12763
|
return {
|
|
13700
12764
|
format: 1,
|
|
13701
12765
|
kind: "forgezero-cloudflare-bootstrap-acceptance",
|
|
13702
12766
|
checkpointFile: absolute,
|
|
13703
12767
|
verifiedAt: new Date().toISOString(),
|
|
13704
|
-
nodes
|
|
13705
|
-
publicDomains
|
|
12768
|
+
nodes
|
|
13706
12769
|
};
|
|
13707
12770
|
}
|
|
13708
12771
|
|
|
@@ -13788,6 +12851,18 @@ function validateBootstrapConfig(value) {
|
|
|
13788
12851
|
if (telemetry.protocol !== "https:" || telemetry.port && telemetry.port !== "443" || telemetry.username || telemetry.password || telemetry.search || telemetry.hash) {
|
|
13789
12852
|
throw new Error("telemetry endpoint must be public HTTPS on port 443 without credentials, query or fragment");
|
|
13790
12853
|
}
|
|
12854
|
+
const deploymentCredentials = value.deploymentCredentials ?? {};
|
|
12855
|
+
if (!deploymentCredentials || typeof deploymentCredentials !== "object" || Array.isArray(deploymentCredentials) || Object.keys(deploymentCredentials).length > 64) {
|
|
12856
|
+
throw new Error("deployment credentials must be a bounded name-to-path object");
|
|
12857
|
+
}
|
|
12858
|
+
for (const [name, path] of Object.entries(deploymentCredentials)) {
|
|
12859
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(name) || typeof path !== "string" || !path.startsWith("/") || /[\r\n:]/.test(path) || !path.endsWith(".cred")) {
|
|
12860
|
+
throw new Error(`deployment credential ${name} must map to an absolute encrypted .cred path`);
|
|
12861
|
+
}
|
|
12862
|
+
}
|
|
12863
|
+
if (value.cloudflareHandoff && (!value.installCloudflared || !value.cloudflareHandoff.handoffFile || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.cloudflareHandoff.nodeName))) {
|
|
12864
|
+
throw new Error("Cloudflare handoff requires cloudflared installation, a checkpoint and a valid node name");
|
|
12865
|
+
}
|
|
13791
12866
|
if (value.kind === "tenant") {
|
|
13792
12867
|
if (!/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/.test(value.realm))
|
|
13793
12868
|
throw new Error("tenant realm is malformed");
|
|
@@ -13867,9 +12942,6 @@ function validateBootstrapConfig(value) {
|
|
|
13867
12942
|
if (!["genesis-derived", "api-token"].includes(value.enrolment.source) || value.enrolment.source === "api-token" && !value.enrolment.tokenFile || value.enrolment.source === "genesis-derived" && value.enrolment.tokenFile) {
|
|
13868
12943
|
throw new Error("platform enrolment source and token file disagree");
|
|
13869
12944
|
}
|
|
13870
|
-
if (value.cloudflareHandoff && (!value.installCloudflared || !value.cloudflareHandoff.handoffFile || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.cloudflareHandoff.nodeName))) {
|
|
13871
|
-
throw new Error("Cloudflare handoff requires cloudflared installation, a checkpoint and a valid node name");
|
|
13872
|
-
}
|
|
13873
12945
|
value.runtime.environment = runtime;
|
|
13874
12946
|
return value;
|
|
13875
12947
|
}
|
|
@@ -14121,7 +13193,7 @@ function bootstrapIdentity(config) {
|
|
|
14121
13193
|
};
|
|
14122
13194
|
}
|
|
14123
13195
|
function bootstrapIdentityDigest(config) {
|
|
14124
|
-
return
|
|
13196
|
+
return createHash2("sha256").update(JSON.stringify(bootstrapIdentity(config))).digest("hex");
|
|
14125
13197
|
}
|
|
14126
13198
|
function parseStoredState(raw) {
|
|
14127
13199
|
let value;
|
|
@@ -14199,7 +13271,7 @@ async function preparePlatformBootstrap(input, host = localBootstrapHost()) {
|
|
|
14199
13271
|
next: "register this read-only deploy key, then run --apply concurrently on all three genesis Agency members"
|
|
14200
13272
|
};
|
|
14201
13273
|
}
|
|
14202
|
-
function stateFor(config) {
|
|
13274
|
+
function stateFor(config, cloudflare, previousCloudflareTunnelId) {
|
|
14203
13275
|
return `${JSON.stringify({
|
|
14204
13276
|
format: 2,
|
|
14205
13277
|
kind: config.kind,
|
|
@@ -14220,7 +13292,11 @@ function stateFor(config) {
|
|
|
14220
13292
|
healthPath: config.runtime.healthPath,
|
|
14221
13293
|
cloudflare: config.runtime.environment.cloudflare,
|
|
14222
13294
|
cloudflared: Boolean(config.cloudflareHandoff)
|
|
14223
|
-
} : {
|
|
13295
|
+
} : {
|
|
13296
|
+
realm: config.realm,
|
|
13297
|
+
cloudflared: Boolean(config.cloudflareHandoff),
|
|
13298
|
+
cloudflareTunnelId: cloudflare?.tunnelId ?? previousCloudflareTunnelId
|
|
13299
|
+
}
|
|
14224
13300
|
}, null, 2)}
|
|
14225
13301
|
`;
|
|
14226
13302
|
}
|
|
@@ -14238,7 +13314,7 @@ async function bootstrapStatus(host = localBootstrapHost()) {
|
|
|
14238
13314
|
units.push("nginx.service");
|
|
14239
13315
|
if (state.kind === "platform" && state.collectorUnit)
|
|
14240
13316
|
units.push(state.collectorUnit);
|
|
14241
|
-
if (state.
|
|
13317
|
+
if (state.cloudflared)
|
|
14242
13318
|
units.push("cloudflared.service");
|
|
14243
13319
|
if (state.kind === "platform" && state.databaseRole !== "none")
|
|
14244
13320
|
units.push("forgezero-db.service", "forgezero-db-verify.service");
|
|
@@ -14255,13 +13331,25 @@ async function bootstrapStatus(host = localBootstrapHost()) {
|
|
|
14255
13331
|
if (!services[socket])
|
|
14256
13332
|
problems.push(`${socket} is missing`);
|
|
14257
13333
|
}
|
|
13334
|
+
if (state.cloudflared) {
|
|
13335
|
+
for (const credential of [TUNNEL_CREDENTIAL, `${CREDS}/cloudflare-kv-token.cred`]) {
|
|
13336
|
+
services[credential] = host.exists(credential);
|
|
13337
|
+
if (!services[credential])
|
|
13338
|
+
problems.push(`${credential} is missing`);
|
|
13339
|
+
}
|
|
13340
|
+
const tunnelId = state.cloudflare?.tunnelId ?? state.cloudflareTunnelId;
|
|
13341
|
+
if (!tunnelId) {
|
|
13342
|
+
services["cloudflared-tunnel"] = false;
|
|
13343
|
+
problems.push("Cloudflare tunnel identity is missing from bootstrap state");
|
|
13344
|
+
} else {
|
|
13345
|
+
const evidence = await inspectCloudflaredTunnel(host, tunnelId);
|
|
13346
|
+
services["cloudflared-tunnel"] = evidence.healthy;
|
|
13347
|
+
if (!evidence.healthy)
|
|
13348
|
+
problems.push(evidence.problem);
|
|
13349
|
+
}
|
|
13350
|
+
}
|
|
14258
13351
|
if (state.kind === "platform") {
|
|
14259
13352
|
if (state.cloudflared) {
|
|
14260
|
-
for (const credential of [TUNNEL_CREDENTIAL, `${CREDS}/cloudflare-kv-token.cred`]) {
|
|
14261
|
-
services[credential] = host.exists(credential);
|
|
14262
|
-
if (!services[credential])
|
|
14263
|
-
problems.push(`${credential} is missing`);
|
|
14264
|
-
}
|
|
14265
13353
|
if (state.cloudflare?.warp) {
|
|
14266
13354
|
for (const credential of [
|
|
14267
13355
|
`${CREDS}/cloudflare-network-token.cred`,
|
|
@@ -14273,15 +13361,6 @@ async function bootstrapStatus(host = localBootstrapHost()) {
|
|
|
14273
13361
|
problems.push(`${credential} is missing`);
|
|
14274
13362
|
}
|
|
14275
13363
|
}
|
|
14276
|
-
if (!state.cloudflare?.tunnelId) {
|
|
14277
|
-
services["cloudflared-tunnel"] = false;
|
|
14278
|
-
problems.push("Cloudflare tunnel identity is missing from bootstrap state");
|
|
14279
|
-
} else {
|
|
14280
|
-
const evidence = await inspectCloudflaredTunnel(host, state.cloudflare.tunnelId);
|
|
14281
|
-
services["cloudflared-tunnel"] = evidence.healthy;
|
|
14282
|
-
if (!evidence.healthy)
|
|
14283
|
-
problems.push(evidence.problem);
|
|
14284
|
-
}
|
|
14285
13364
|
}
|
|
14286
13365
|
const [blue, green] = await Promise.all([
|
|
14287
13366
|
host.exec(["systemctl", "is-active", "--quiet", "forgezero@blue.service"]),
|
|
@@ -14350,20 +13429,21 @@ async function applyBootstrap(input, host = localBootstrapHost()) {
|
|
|
14350
13429
|
if (host.exists(STATE_PATH))
|
|
14351
13430
|
installed = parseStoredState(host.read(STATE_PATH));
|
|
14352
13431
|
let cloudflare;
|
|
14353
|
-
if (config.
|
|
13432
|
+
if (config.cloudflareHandoff) {
|
|
14354
13433
|
if (host.exists(config.cloudflareHandoff.handoffFile)) {
|
|
14355
13434
|
cloudflare = await readCloudflareHostHandoff(config.cloudflareHandoff.handoffFile, config.cloudflareHandoff.nodeName);
|
|
14356
|
-
} else if (installed?.cloudflare) {
|
|
13435
|
+
} else if (config.kind === "platform" && installed?.cloudflare) {
|
|
14357
13436
|
config.runtime.environment.cloudflare = installed.cloudflare;
|
|
14358
13437
|
config.runtime.environment = validatePlatformSharedEnvironment(config.runtime.environment);
|
|
14359
|
-
} else {
|
|
13438
|
+
} else if (!(config.kind === "tenant" && installed?.cloudflareTunnelId)) {
|
|
14360
13439
|
throw new Error("node-specific Cloudflare host handoff is missing before credential sealing");
|
|
14361
13440
|
}
|
|
14362
13441
|
}
|
|
13442
|
+
if (cloudflare && cloudflare.hostname !== config.nodeHostname) {
|
|
13443
|
+
throw new Error("Cloudflare handoff hostname disagrees with node hostname");
|
|
13444
|
+
}
|
|
14363
13445
|
if (cloudflare && config.kind === "platform") {
|
|
14364
13446
|
const expected = config.runtime.environment.cloudflare;
|
|
14365
|
-
if (cloudflare.hostname !== config.nodeHostname)
|
|
14366
|
-
throw new Error("Cloudflare handoff hostname disagrees with platform node hostname");
|
|
14367
13447
|
if (expected && (cloudflare.service !== expected.tunnelService || cloudflare.accountId !== expected.accountId || cloudflare.zoneId !== expected.zoneId || cloudflare.kvNamespaceId !== expected.kvNamespaceId || cloudflare.tunnelId !== expected.tunnelId)) {
|
|
14368
13448
|
throw new Error("Cloudflare handoff disagrees with immutable platform runtime coordinates");
|
|
14369
13449
|
}
|
|
@@ -14405,6 +13485,9 @@ async function applyBootstrap(input, host = localBootstrapHost()) {
|
|
|
14405
13485
|
await seal(host, "warp-auth-client-id", WARP_CLIENT_ID_CREDENTIAL, cloudflare.warp.clientId);
|
|
14406
13486
|
await seal(host, "warp-auth-client-secret", WARP_CLIENT_SECRET_CREDENTIAL, cloudflare.warp.clientSecret);
|
|
14407
13487
|
}
|
|
13488
|
+
if (cloudflare && !host.exists(`${CREDS}/cloudflare-kv-token.cred`)) {
|
|
13489
|
+
await seal(host, "cloudflare-kv-token", `${CREDS}/cloudflare-kv-token.cred`, cloudflare.kvRuntimeToken);
|
|
13490
|
+
}
|
|
14408
13491
|
await host.installAgent(config);
|
|
14409
13492
|
if (config.kind === "platform" && config.firewall.enabled) {
|
|
14410
13493
|
await host.ensureSoftware(plan.software.filter(({ id: id2 }) => id2 === "ufw"));
|
|
@@ -14444,7 +13527,6 @@ async function applyBootstrap(input, host = localBootstrapHost()) {
|
|
|
14444
13527
|
}
|
|
14445
13528
|
}
|
|
14446
13529
|
if (cloudflare) {
|
|
14447
|
-
await seal(host, "cloudflare-kv-token", `${CREDS}/cloudflare-kv-token.cred`, cloudflare.kvRuntimeToken);
|
|
14448
13530
|
if (cloudflare.privateNetworkRuntimeToken)
|
|
14449
13531
|
await seal(host, "cloudflare-network-token", `${CREDS}/cloudflare-network-token.cred`, cloudflare.privateNetworkRuntimeToken);
|
|
14450
13532
|
}
|
|
@@ -14465,16 +13547,23 @@ async function applyBootstrap(input, host = localBootstrapHost()) {
|
|
|
14465
13547
|
bluePort: runtime.bluePort,
|
|
14466
13548
|
greenPort: runtime.greenPort,
|
|
14467
13549
|
collectorUnit: runtime.environment.otlpCollectorUnit,
|
|
14468
|
-
credentials
|
|
13550
|
+
credentials,
|
|
13551
|
+
capacityEnvironmentFile: "/etc/forgezero/capacity.env"
|
|
13552
|
+
});
|
|
13553
|
+
const edge = renderPlatformNginx({
|
|
13554
|
+
publicPort: runtime.environment.publicApiPort,
|
|
13555
|
+
initialSlotPort: runtime.bluePort,
|
|
13556
|
+
concurrencyLimit: runtime.environment.concurrencyLimit,
|
|
13557
|
+
workerDrainSeconds: Math.ceil(runtime.environment.drainDeadlineMs / 1000)
|
|
14469
13558
|
});
|
|
14470
|
-
const edge = renderPlatformNginx({ publicPort: runtime.environment.publicApiPort, initialSlotPort: runtime.bluePort });
|
|
14471
13559
|
const activation = renderPlatformActivationFiles({
|
|
14472
13560
|
root: config.deployRoot ?? "/opt/forgezero",
|
|
14473
13561
|
serviceUser: runtime.serviceUser,
|
|
14474
13562
|
bluePort: runtime.bluePort,
|
|
14475
13563
|
greenPort: runtime.greenPort,
|
|
14476
13564
|
healthPath: runtime.healthPath,
|
|
14477
|
-
keepReleases: runtime.keepReleases
|
|
13565
|
+
keepReleases: runtime.keepReleases,
|
|
13566
|
+
drainDeadlineMs: runtime.environment.drainDeadlineMs
|
|
14478
13567
|
});
|
|
14479
13568
|
await checked(host, ["useradd", "--system", "--no-create-home", "--shell", "/usr/sbin/nologin", runtime.serviceUser], "API service account").catch(async () => {
|
|
14480
13569
|
await checked(host, ["id", runtime.serviceUser], "existing API service account");
|
|
@@ -14482,6 +13571,10 @@ async function applyBootstrap(input, host = localBootstrapHost()) {
|
|
|
14482
13571
|
host.mkdir(runtime.environment.sharedDirectory, 488);
|
|
14483
13572
|
host.mkdir(runtime.slotsDirectory, 493);
|
|
14484
13573
|
host.write(envPath, renderPlatformSharedEnvironment(runtime.environment), 416);
|
|
13574
|
+
if (!host.exists("/etc/forgezero/capacity.env")) {
|
|
13575
|
+
host.write("/etc/forgezero/capacity.env", `FZ_CONCURRENCY_LIMIT=${runtime.environment.concurrencyLimit}
|
|
13576
|
+
`, 420);
|
|
13577
|
+
}
|
|
14485
13578
|
await checked(host, ["chown", `root:${runtime.serviceUser}`, runtime.environment.sharedDirectory, envPath], "runtime ownership");
|
|
14486
13579
|
host.write("/etc/systemd/system/forgezero@.service", units.template, 420);
|
|
14487
13580
|
host.write("/etc/systemd/system/forgezero@blue.service.d/port.conf", units.dropIns.blue, 420);
|
|
@@ -14547,7 +13640,7 @@ async function applyBootstrap(input, host = localBootstrapHost()) {
|
|
|
14547
13640
|
host.remove(config.enrolment.tokenFile);
|
|
14548
13641
|
}
|
|
14549
13642
|
}
|
|
14550
|
-
if (config.
|
|
13643
|
+
if (config.cloudflareHandoff) {
|
|
14551
13644
|
if (!host.exists(TUNNEL_CREDENTIAL) && cloudflare) {
|
|
14552
13645
|
await seal(host, "cloudflared-token", TUNNEL_CREDENTIAL, cloudflare.connectorToken);
|
|
14553
13646
|
}
|
|
@@ -14556,17 +13649,17 @@ async function applyBootstrap(input, host = localBootstrapHost()) {
|
|
|
14556
13649
|
host.write("/etc/systemd/system/cloudflared.service", tunnelUnit(), 420);
|
|
14557
13650
|
await checked(host, ["systemctl", "daemon-reload"], "cloudflared unit reload");
|
|
14558
13651
|
await checked(host, ["systemctl", "enable", "--now", "cloudflared.service"], "cloudflared connector supervision");
|
|
14559
|
-
const tunnelId = config.runtime.environment.cloudflare?.tunnelId;
|
|
13652
|
+
const tunnelId = cloudflare?.tunnelId ?? installed?.cloudflareTunnelId ?? (config.kind === "platform" ? config.runtime.environment.cloudflare?.tunnelId : undefined);
|
|
14560
13653
|
if (!tunnelId)
|
|
14561
13654
|
throw new Error("Cloudflare tunnel identity is missing after handoff validation");
|
|
14562
13655
|
await waitForCloudflaredTunnel(host, tunnelId);
|
|
14563
13656
|
}
|
|
14564
|
-
host.write(STATE_PATH, stateFor(config), 384);
|
|
13657
|
+
host.write(STATE_PATH, stateFor(config, cloudflare, installed?.cloudflareTunnelId), 384);
|
|
14565
13658
|
const status = await bootstrapStatus(host);
|
|
14566
13659
|
if (!status.initialized)
|
|
14567
13660
|
throw new Error(`bootstrap verification failed: ${status.problems.join("; ")}`);
|
|
14568
13661
|
host.remove(INTENT_PATH);
|
|
14569
|
-
if (cloudflare && config.
|
|
13662
|
+
if (cloudflare && config.cloudflareHandoff) {
|
|
14570
13663
|
host.remove(config.cloudflareHandoff.handoffFile);
|
|
14571
13664
|
}
|
|
14572
13665
|
let launch;
|
|
@@ -14614,6 +13707,7 @@ function strictBootstrapDocument(value) {
|
|
|
14614
13707
|
"realm",
|
|
14615
13708
|
"enrolTokenFile",
|
|
14616
13709
|
"software",
|
|
13710
|
+
"deploymentCredentials",
|
|
14617
13711
|
"bootstrapRunner"
|
|
14618
13712
|
], "bootstrap config");
|
|
14619
13713
|
if (root.kind === "platform") {
|
|
@@ -14680,9 +13774,11 @@ function strictBootstrapDocument(value) {
|
|
|
14680
13774
|
exactKeys2(environment.cloudflare.warp, ["organization", "virtualNetworkId", "deviceProfileId"], "Cloudflare WARP runtime config");
|
|
14681
13775
|
}
|
|
14682
13776
|
} else if (root.kind === "tenant") {
|
|
13777
|
+
if (root.cloudflareHandoff !== undefined)
|
|
13778
|
+
exactKeys2(root.cloudflareHandoff, ["handoffFile", "nodeName"], "Cloudflare handoff");
|
|
14683
13779
|
if (root.bootstrapRunner !== undefined)
|
|
14684
13780
|
exactKeys2(root.bootstrapRunner, ["sshPrivateKeyFile", "targetTelemetryEndpoint"], "bootstrap runner config");
|
|
14685
|
-
for (const key of ["environment", "profile", "computeReference", "database", "enrolment", "runtime"
|
|
13781
|
+
for (const key of ["environment", "profile", "computeReference", "database", "enrolment", "runtime"]) {
|
|
14686
13782
|
if (root[key] !== undefined && key !== "profile")
|
|
14687
13783
|
throw new Error(`tenant bootstrap cannot contain ${key}`);
|
|
14688
13784
|
}
|
|
@@ -14771,6 +13867,7 @@ function localBootstrapHost() {
|
|
|
14771
13867
|
branch: config.branch,
|
|
14772
13868
|
profile: config.kind === "platform" ? config.profile : config.profile,
|
|
14773
13869
|
deployRoot,
|
|
13870
|
+
deploymentCredentials: config.deploymentCredentials,
|
|
14774
13871
|
publicApiUrl: config.apiUrl,
|
|
14775
13872
|
gitCredentialPath: "/etc/forgezero/creds/git-deploy-key.cred",
|
|
14776
13873
|
gitPublicKeyPath: "/etc/forgezero/git/deploy.pub",
|
|
@@ -14867,20 +13964,8 @@ function readCloudflareBootstrapCommandConfig(path, mode) {
|
|
|
14867
13964
|
"hostname",
|
|
14868
13965
|
"service",
|
|
14869
13966
|
"tunnelName",
|
|
14870
|
-
"
|
|
14871
|
-
"
|
|
14872
|
-
"serviceTokenName",
|
|
14873
|
-
"policyName",
|
|
14874
|
-
"applicationName",
|
|
14875
|
-
"workerDirectory",
|
|
14876
|
-
"workerMain",
|
|
14877
|
-
"workerCompatibilityDate",
|
|
14878
|
-
"publicDomains",
|
|
14879
|
-
"createRuntimeTokens",
|
|
14880
|
-
"createPrivateNetworkRuntimeToken",
|
|
14881
|
-
"runtimeTokenNamePrefix",
|
|
14882
|
-
"nodes",
|
|
14883
|
-
"privateNetwork"
|
|
13967
|
+
"kvNamespaceId",
|
|
13968
|
+
"nodes"
|
|
14884
13969
|
], "Cloudflare bootstrap coordinates");
|
|
14885
13970
|
if (coordinateSource.nodes !== undefined) {
|
|
14886
13971
|
if (!Array.isArray(coordinateSource.nodes))
|
|
@@ -14890,38 +13975,21 @@ function readCloudflareBootstrapCommandConfig(path, mode) {
|
|
|
14890
13975
|
"nodeName",
|
|
14891
13976
|
"hostname",
|
|
14892
13977
|
"service",
|
|
14893
|
-
"tunnelName"
|
|
14894
|
-
"applicationName",
|
|
14895
|
-
"privateAddress"
|
|
13978
|
+
"tunnelName"
|
|
14896
13979
|
], "Cloudflare bootstrap node");
|
|
14897
13980
|
}
|
|
14898
13981
|
}
|
|
14899
|
-
|
|
14900
|
-
exactKeys3(record2(coordinateSource.privateNetwork, "Cloudflare bootstrap privateNetwork"), [
|
|
14901
|
-
"warpOrganization",
|
|
14902
|
-
"virtualNetworkName",
|
|
14903
|
-
"deviceProfileName",
|
|
14904
|
-
"enrollmentApplicationName",
|
|
14905
|
-
"deviceProfilePrecedence"
|
|
14906
|
-
], "Cloudflare bootstrap privateNetwork");
|
|
14907
|
-
}
|
|
14908
|
-
if (typeof coordinateSource.workerDirectory !== "string" || !coordinateSource.workerDirectory.trim()) {
|
|
14909
|
-
throw new Error("Cloudflare bootstrap coordinates.workerDirectory is required");
|
|
14910
|
-
}
|
|
14911
|
-
const coordinates = validateCloudflareBootstrapCoordinates({
|
|
14912
|
-
...coordinateSource,
|
|
14913
|
-
workerDirectory: resolve3(baseDirectory, coordinateSource.workerDirectory)
|
|
14914
|
-
});
|
|
13982
|
+
const coordinates = validateCloudflareBootstrapCoordinates(coordinateSource);
|
|
14915
13983
|
let tokenFiles;
|
|
14916
13984
|
if (input.tokenFiles !== undefined) {
|
|
14917
13985
|
const source = record2(input.tokenFiles, "Cloudflare bootstrap tokenFiles");
|
|
14918
13986
|
const keys = [
|
|
14919
13987
|
"apiTokenFile",
|
|
13988
|
+
"managementApiTokenFile",
|
|
13989
|
+
"runtimeApiTokenFile",
|
|
14920
13990
|
"tunnelApiTokenFile",
|
|
14921
13991
|
"dnsApiTokenFile",
|
|
14922
|
-
"kvApiTokenFile"
|
|
14923
|
-
"accessApiTokenFile",
|
|
14924
|
-
"workerApiTokenFile"
|
|
13992
|
+
"kvApiTokenFile"
|
|
14925
13993
|
];
|
|
14926
13994
|
exactKeys3(source, keys, "Cloudflare bootstrap tokenFiles");
|
|
14927
13995
|
for (const key of keys) {
|
|
@@ -14942,8 +14010,8 @@ function readCloudflareBootstrapCommandConfig(path, mode) {
|
|
|
14942
14010
|
};
|
|
14943
14011
|
}
|
|
14944
14012
|
async function runCloudflareBootstrapCommand(configPath, apply, dependencies = {}) {
|
|
14945
|
-
const
|
|
14946
|
-
const evidence = await (dependencies.run ?? runAttendedCloudflareBootstrap)(
|
|
14013
|
+
const request = readCloudflareBootstrapCommandConfig(configPath, apply ? "apply" : "plan");
|
|
14014
|
+
const evidence = await (dependencies.run ?? runAttendedCloudflareBootstrap)(request);
|
|
14947
14015
|
(dependencies.write ?? ((text3) => process.stdout.write(text3)))(`${JSON.stringify(evidence, null, 2)}
|
|
14948
14016
|
`);
|
|
14949
14017
|
return evidence;
|
|
@@ -14956,7 +14024,7 @@ async function runCloudflareBootstrapVerificationCommand(checkpointPath, depende
|
|
|
14956
14024
|
}
|
|
14957
14025
|
|
|
14958
14026
|
// src/metal-bootstrap.ts
|
|
14959
|
-
import { createHash as
|
|
14027
|
+
import { createHash as createHash3, randomBytes as randomBytes8 } from "crypto";
|
|
14960
14028
|
import {
|
|
14961
14029
|
chmodSync as chmodSync3,
|
|
14962
14030
|
chownSync,
|
|
@@ -14973,7 +14041,7 @@ import {
|
|
|
14973
14041
|
writeFileSync as writeFileSync6
|
|
14974
14042
|
} from "fs";
|
|
14975
14043
|
import { dirname as dirname7, isAbsolute as isAbsolute2, join as join8, resolve as resolve4 } from "path";
|
|
14976
|
-
import { isIP as
|
|
14044
|
+
import { isIP as isIP3 } from "net";
|
|
14977
14045
|
|
|
14978
14046
|
// src/metal-isolation.ts
|
|
14979
14047
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
@@ -14981,7 +14049,7 @@ import { join as join7 } from "path";
|
|
|
14981
14049
|
|
|
14982
14050
|
// src/metal-provision.ts
|
|
14983
14051
|
import { dirname as dirname6, isAbsolute, join as join6 } from "path";
|
|
14984
|
-
import { isIP as
|
|
14052
|
+
import { isIP as isIP2 } from "net";
|
|
14985
14053
|
|
|
14986
14054
|
// src/ubuntu.ts
|
|
14987
14055
|
var SUPPORTED_GUEST_IMAGE = Object.freeze({
|
|
@@ -15039,7 +14107,7 @@ function validateMetalProfile(profile) {
|
|
|
15039
14107
|
} catch {
|
|
15040
14108
|
throw new MetalProvisionError("Agent telemetry endpoint must be an absolute public HTTPS URL");
|
|
15041
14109
|
}
|
|
15042
|
-
if (telemetryEndpoint.protocol !== "https:" || telemetryEndpoint.username || telemetryEndpoint.password || telemetryEndpoint.search || telemetryEndpoint.hash ||
|
|
14110
|
+
if (telemetryEndpoint.protocol !== "https:" || telemetryEndpoint.username || telemetryEndpoint.password || telemetryEndpoint.search || telemetryEndpoint.hash || isIP2(telemetryEndpoint.hostname) !== 0 || !telemetryEndpoint.hostname.includes(".") || telemetryEndpoint.hostname === "localhost" || telemetryEndpoint.hostname.endsWith(".local"))
|
|
15043
14111
|
throw new MetalProvisionError("Agent telemetry endpoint must be a public HTTPS DNS coordinate without credentials, query or fragment");
|
|
15044
14112
|
const imageKeys = Object.keys(profile.images);
|
|
15045
14113
|
if (imageKeys.length !== 1 || imageKeys[0] !== SUPPORTED_GUEST_IMAGE.key || profile.images[SUPPORTED_GUEST_IMAGE.key]?.sha256 !== SUPPORTED_GUEST_IMAGE.sha256) {
|
|
@@ -15291,13 +14359,13 @@ function validateMetalBootstrapConfig(config) {
|
|
|
15291
14359
|
} catch {
|
|
15292
14360
|
throw new MetalBootstrapError("metal API must be a public HTTPS origin");
|
|
15293
14361
|
}
|
|
15294
|
-
if (api.protocol !== "https:" || api.username || api.password || api.pathname !== "/" || api.search || api.hash ||
|
|
14362
|
+
if (api.protocol !== "https:" || api.username || api.password || api.pathname !== "/" || api.search || api.hash || isIP3(api.hostname) !== 0 || !api.hostname.includes(".") || api.hostname.endsWith(".local")) {
|
|
15295
14363
|
throw new MetalBootstrapError("metal API must be a credential-free public HTTPS origin");
|
|
15296
14364
|
}
|
|
15297
|
-
if (config.profile.nameservers?.some((address) =>
|
|
14365
|
+
if (config.profile.nameservers?.some((address) => isIP3(address) === 0)) {
|
|
15298
14366
|
throw new MetalBootstrapError("metal nameservers must be literal IP addresses");
|
|
15299
14367
|
}
|
|
15300
|
-
if (
|
|
14368
|
+
if (isIP3(`${config.profile.subnetPrefix}.1`) !== 4 || isIP3(config.profile.gateway) !== 4 || !config.profile.gateway.startsWith(`${config.profile.subnetPrefix}.`)) {
|
|
15301
14369
|
throw new MetalBootstrapError("metal gateway must be an IPv4 address in the reviewed subnet");
|
|
15302
14370
|
}
|
|
15303
14371
|
if (config.profile.confidential && (!Number.isSafeInteger(config.profile.confidential.cbitpos) || config.profile.confidential.cbitpos < 1 || config.profile.confidential.cbitpos > 63 || !Number.isSafeInteger(config.profile.confidential.reducedPhysBits) || config.profile.confidential.reducedPhysBits < 0 || config.profile.confidential.reducedPhysBits > 63 || !/^0x[0-9a-fA-F]{1,16}$/.test(config.profile.confidential.policy)))
|
|
@@ -15714,7 +14782,7 @@ async function applyMetalBootstrap(config, options) {
|
|
|
15714
14782
|
initializedAt: new Date().toISOString(),
|
|
15715
14783
|
role: "metal",
|
|
15716
14784
|
metalHostname: config.metalHostname,
|
|
15717
|
-
profileSha256:
|
|
14785
|
+
profileSha256: createHash3("sha256").update(JSON.stringify(config.profile)).digest("hex")
|
|
15718
14786
|
};
|
|
15719
14787
|
atomicWrite2(STATE_PATH2, `${JSON.stringify(state, null, 2)}
|
|
15720
14788
|
`, 384);
|
|
@@ -15741,7 +14809,7 @@ async function metalBootstrapStatus(exec = defaultExec2) {
|
|
|
15741
14809
|
const { metalHostname: profileHostname, hostTelemetryEndpoint, hostTelemetryUnit, ...profile } = persisted;
|
|
15742
14810
|
validateMetalProfile(profile);
|
|
15743
14811
|
profileValid = true;
|
|
15744
|
-
profileSha256 =
|
|
14812
|
+
profileSha256 = createHash3("sha256").update(JSON.stringify(profile)).digest("hex");
|
|
15745
14813
|
if (!/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(profileHostname) || hostTelemetryEndpoint !== "http://127.0.0.1:4318") {
|
|
15746
14814
|
problems.push("persisted metal host coordinates are invalid");
|
|
15747
14815
|
}
|
|
@@ -16020,27 +15088,6 @@ async function api(options, path, init) {
|
|
|
16020
15088
|
body = null;
|
|
16021
15089
|
}
|
|
16022
15090
|
const challenge = body?.security;
|
|
16023
|
-
if (response.status === 428 && challenge?.scope === "action" && challenge.requestKey && custodyIdentity && !path.startsWith("/security/step-up/")) {
|
|
16024
|
-
const proved = await proveWithAgent(options, challenge.requestKey);
|
|
16025
|
-
if (proved) {
|
|
16026
|
-
const replay = await fetch(`${options.api}${base}${path}`, {
|
|
16027
|
-
method: init?.method ?? "GET",
|
|
16028
|
-
headers: {
|
|
16029
|
-
...requestHeaders(options.api, sessionCookie),
|
|
16030
|
-
"x-security-request-key": challenge.requestKey
|
|
16031
|
-
},
|
|
16032
|
-
body: init?.body === undefined ? undefined : JSON.stringify(init.body)
|
|
16033
|
-
});
|
|
16034
|
-
captureSession(replay);
|
|
16035
|
-
let replayed = null;
|
|
16036
|
-
try {
|
|
16037
|
-
replayed = await replay.json();
|
|
16038
|
-
} catch {
|
|
16039
|
-
replayed = null;
|
|
16040
|
-
}
|
|
16041
|
-
return { status: replay.status, body: replayed };
|
|
16042
|
-
}
|
|
16043
|
-
}
|
|
16044
15091
|
if (response.status === 428 && challenge?.scope === "action" && challenge.requestKey && storedSession && !path.startsWith("/security/step-up/")) {
|
|
16045
15092
|
const proved = await proveWithBrowser(options, challenge.requestKey, base);
|
|
16046
15093
|
if (proved) {
|
|
@@ -16124,27 +15171,6 @@ async function proveWithBrowser(options, requestKey, base) {
|
|
|
16124
15171
|
}
|
|
16125
15172
|
return false;
|
|
16126
15173
|
}
|
|
16127
|
-
var custodyIdentity = null;
|
|
16128
|
-
function useCustodyIdentity(identity, socketPath) {
|
|
16129
|
-
custodyIdentity = { identity, socketPath };
|
|
16130
|
-
}
|
|
16131
|
-
async function proveWithAgent(options, requestKey) {
|
|
16132
|
-
if (!custodyIdentity)
|
|
16133
|
-
return false;
|
|
16134
|
-
const begun = await api(options, "/security/step-up/ssh/begin", {
|
|
16135
|
-
method: "POST",
|
|
16136
|
-
body: { requestKey }
|
|
16137
|
-
});
|
|
16138
|
-
const nonce = begun.body?.nonce;
|
|
16139
|
-
if (begun.status !== 200 || !nonce)
|
|
16140
|
-
return false;
|
|
16141
|
-
const signature = await signWithIdentity(custodyIdentity.identity, new Uint8Array(Buffer.from(nonce, "base64")), custodyIdentity.socketPath);
|
|
16142
|
-
const proved = await api(options, "/security/step-up/ssh/prove", {
|
|
16143
|
-
method: "POST",
|
|
16144
|
-
body: { requestKey, signature: Buffer.from(signature).toString("base64") }
|
|
16145
|
-
});
|
|
16146
|
-
return proved.status === 200;
|
|
16147
|
-
}
|
|
16148
15174
|
function hydrateSession(options) {
|
|
16149
15175
|
const active = activeSession();
|
|
16150
15176
|
if (!options.apiExplicit && active)
|
|
@@ -16373,37 +15399,6 @@ async function cmdApi(options, args) {
|
|
|
16373
15399
|
return 1;
|
|
16374
15400
|
}
|
|
16375
15401
|
}
|
|
16376
|
-
async function cmdKeys(options) {
|
|
16377
|
-
const identities = await usableIdentities(options.socket);
|
|
16378
|
-
if (options.json) {
|
|
16379
|
-
out.line(JSON.stringify(identities.map(({ fingerprint, comment, responseMs }) => ({
|
|
16380
|
-
fingerprint,
|
|
16381
|
-
comment,
|
|
16382
|
-
responseMs
|
|
16383
|
-
})), null, 2));
|
|
16384
|
-
return 0;
|
|
16385
|
-
}
|
|
16386
|
-
if (identities.length === 0) {
|
|
16387
|
-
out.fail("No usable Ed25519 keys in the agent.");
|
|
16388
|
-
out.line();
|
|
16389
|
-
out.step("Custody needs a key that signs DETERMINISTICALLY, so Ed25519 only.");
|
|
16390
|
-
out.step("Add one with: ssh-add ~/.ssh/id_ed25519");
|
|
16391
|
-
out.line();
|
|
16392
|
-
out.step("Note that keys which hang \u2014 forwarded agents whose upstream is");
|
|
16393
|
-
out.step("gone, or confirm-on-use keys with nobody at the terminal \u2014 are");
|
|
16394
|
-
out.step("skipped here rather than listed, so this can be shorter than");
|
|
16395
|
-
out.step("`ssh-add -l`.");
|
|
16396
|
-
return 1;
|
|
16397
|
-
}
|
|
16398
|
-
out.line("Usable custody keys:");
|
|
16399
|
-
out.line();
|
|
16400
|
-
identities.forEach((identity, index) => {
|
|
16401
|
-
out.line(` ${index + 1}. ${identity.fingerprint}`);
|
|
16402
|
-
out.line(` ${identity.comment || "(no comment)"} ${identity.responseMs}ms`);
|
|
16403
|
-
});
|
|
16404
|
-
out.line();
|
|
16405
|
-
return 0;
|
|
16406
|
-
}
|
|
16407
15402
|
async function cmdStatus(options) {
|
|
16408
15403
|
try {
|
|
16409
15404
|
const health = await api(options, "/health");
|
|
@@ -16704,7 +15699,7 @@ function interactiveBootstrap(kind) {
|
|
|
16704
15699
|
async function cmdBootstrap(options, args) {
|
|
16705
15700
|
try {
|
|
16706
15701
|
const operation = args[0] ?? "status";
|
|
16707
|
-
if (operation === "platform" && args[1] === "cloudflare") {
|
|
15702
|
+
if ((operation === "platform" || operation === "tenant") && args[1] === "cloudflare") {
|
|
16708
15703
|
if (!options.bootstrapConfigPath) {
|
|
16709
15704
|
throw new Error("Cloudflare bootstrap requires --bootstrap-config <owner-only-cloudflare-json>");
|
|
16710
15705
|
}
|
|
@@ -16781,7 +15776,7 @@ async function cmdBootstrap(options, args) {
|
|
|
16781
15776
|
return 0;
|
|
16782
15777
|
}
|
|
16783
15778
|
if (!["platform", "tenant", "repair"].includes(operation)) {
|
|
16784
|
-
throw new Error("Usage: fz bootstrap platform [prepare|cloudflare [verify]]|tenant|metal|status|repair [--bootstrap-config <path>] [--apply]");
|
|
15779
|
+
throw new Error("Usage: fz bootstrap platform [prepare|cloudflare [verify]]|tenant [cloudflare [verify]]|metal|status|repair [--bootstrap-config <path>] [--apply]");
|
|
16785
15780
|
}
|
|
16786
15781
|
const config = options.bootstrapConfigPath ? readBootstrapConfig(options.bootstrapConfigPath) : operation === "repair" ? (() => {
|
|
16787
15782
|
throw new Error("repair requires --bootstrap-config so immutable coordinates are revalidated");
|
|
@@ -17186,7 +16181,6 @@ function usage() {
|
|
|
17186
16181
|
fz api <method> <path> Use any authorized realm API from a project or AI agent
|
|
17187
16182
|
fz ui routes [search] Discover the current account's authorized UI actions
|
|
17188
16183
|
fz ui <method> <path> Run an authorized UI action without visiting the site
|
|
17189
|
-
fz keys List agent keys usable for custody
|
|
17190
16184
|
fz status Platform, ceremony and vault state
|
|
17191
16185
|
fz genesis Open first-founder onboarding, link this CLI, then
|
|
17192
16186
|
continue the custodian ceremony in the browser
|
|
@@ -17203,12 +16197,14 @@ function usage() {
|
|
|
17203
16197
|
Generate the machine deploy key before private Git deployment
|
|
17204
16198
|
fz bootstrap platform Install/repair a typed elastic platform compute
|
|
17205
16199
|
fz bootstrap platform cloudflare
|
|
17206
|
-
Plan/apply
|
|
17207
|
-
|
|
16200
|
+
Plan/apply attended Tunnel and DNS reconciliation
|
|
16201
|
+
using supplied management and KV runtime token files
|
|
17208
16202
|
fz bootstrap platform cloudflare verify
|
|
17209
|
-
From the operator laptop, prove every
|
|
17210
|
-
|
|
16203
|
+
From the operator laptop, prove every ordinary public
|
|
16204
|
+
node /api/health using the 0600 checkpoint
|
|
17211
16205
|
fz bootstrap tenant Install/enrol reusable tenant compute tooling
|
|
16206
|
+
fz bootstrap tenant cloudflare
|
|
16207
|
+
Use the same attended Tunnel/DNS resource phase for tenant origins
|
|
17212
16208
|
fz bootstrap metal Plan/install an identity-only physical provisioner
|
|
17213
16209
|
fz bootstrap status Verify persisted profile and supervised units
|
|
17214
16210
|
fz bootstrap repair Reapply an explicitly supplied reviewed config
|
|
@@ -17287,8 +16283,8 @@ function usage() {
|
|
|
17287
16283
|
fz unlock opens the signed-in custodian's own phrase envelope. Supply the
|
|
17288
16284
|
24-word BIP-39 phrase by owner-only file or stdin; it is never accepted in
|
|
17289
16285
|
argv. Each custodian runs it in their own authenticated session; no process
|
|
17290
|
-
collects several custodians' phrases.
|
|
17291
|
-
|
|
16286
|
+
collects several custodians' phrases. Fresh privileged actions use the
|
|
16287
|
+
browser passkey/TOTP flow; SSH is transport and Git interoperability only.
|
|
17292
16288
|
`);
|
|
17293
16289
|
}
|
|
17294
16290
|
if (import.meta.main) {
|
|
@@ -17320,9 +16316,6 @@ async function runCli() {
|
|
|
17320
16316
|
case "ui":
|
|
17321
16317
|
code = await cmdApi(options, args);
|
|
17322
16318
|
break;
|
|
17323
|
-
case "keys":
|
|
17324
|
-
code = await cmdKeys(options);
|
|
17325
|
-
break;
|
|
17326
16319
|
case "status":
|
|
17327
16320
|
code = await cmdStatus(options);
|
|
17328
16321
|
break;
|
|
@@ -17367,7 +16360,6 @@ async function runCli() {
|
|
|
17367
16360
|
process.exit(code);
|
|
17368
16361
|
}
|
|
17369
16362
|
export {
|
|
17370
|
-
useCustodyIdentity,
|
|
17371
16363
|
targetCoordinate,
|
|
17372
16364
|
safeApiPath,
|
|
17373
16365
|
requestHeaders
|