querysub 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/-c-identity/IdentityController.ts +9 -7
- package/src/-d-trust/NetworkTrust2.ts +38 -18
- package/src/3-path-functions/pathFunctionLoader.ts +0 -4
- package/src/4-querysub/QuerysubController.ts +1 -1
- package/src/4-querysub/querysubPrediction.ts +16 -4
- package/src/5-diagnostics/synchronousLagTracking.ts +3 -0
- package/src/diagnostics/trackResources.ts +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
25
25
|
"pako": "^2.1.0",
|
|
26
26
|
"preact": "^10.11.3",
|
|
27
|
-
"socket-function": "^0.
|
|
27
|
+
"socket-function": "^0.37.0",
|
|
28
28
|
"terser": "^5.31.0",
|
|
29
29
|
"typesafecss": "^0.6.3",
|
|
30
30
|
"yaml": "^2.5.0",
|
|
@@ -14,6 +14,7 @@ import { getShortNumber } from "../bits";
|
|
|
14
14
|
import { measureFnc, measureWrap } from "socket-function/src/profiling/measure";
|
|
15
15
|
import { timeoutToError } from "../errors";
|
|
16
16
|
import { delay } from "socket-function/src/batching";
|
|
17
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
17
18
|
|
|
18
19
|
let callerInfo = new Map<CallerContext, {
|
|
19
20
|
reconnectNodeId: string | undefined;
|
|
@@ -114,6 +115,7 @@ class IdentityControllerBase {
|
|
|
114
115
|
// NodeJS will throw/ignore the peer cert if it isn't trusted).
|
|
115
116
|
@measureFnc
|
|
116
117
|
public async changeIdentity(signature: string, payload: ChangeIdentityPayload) {
|
|
118
|
+
let time = Date.now();
|
|
117
119
|
const caller = SocketFunction.getCaller();
|
|
118
120
|
// Verify it wasn't signed too long ago (to make signature stealing WAY more difficult).
|
|
119
121
|
const signedThreshold = Date.now() - 1000 * 30;
|
|
@@ -152,6 +154,8 @@ class IdentityControllerBase {
|
|
|
152
154
|
pubKey,
|
|
153
155
|
pubKeyShort: getShortNumber(pubKey),
|
|
154
156
|
});
|
|
157
|
+
|
|
158
|
+
console.log(`Authenticated identity for ${caller.nodeId} to ${reconnectNodeId || caller.nodeId} in ${formatTime(Date.now() - time)}, at ${Date.now()}`);
|
|
155
159
|
}
|
|
156
160
|
}
|
|
157
161
|
|
|
@@ -182,13 +186,11 @@ const changeIdentityOnce = cache(measureWrap(async function changeIdentityOnce(c
|
|
|
182
186
|
mountedPort: getNodeIdLocation(SocketFunction.mountedNodeId)?.port,
|
|
183
187
|
};
|
|
184
188
|
let signature = sign(threadKeyCert, payload);
|
|
185
|
-
await
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
);
|
|
191
|
-
}, `callChangeIdentity|${nodeId}`)();
|
|
189
|
+
await timeoutToError(
|
|
190
|
+
10 * 1000,
|
|
191
|
+
IdentityController.nodes[nodeId].changeIdentity(signature, payload),
|
|
192
|
+
() => new Error(`Timeout calling changeIdentity for ${nodeId}`)
|
|
193
|
+
);
|
|
192
194
|
}));
|
|
193
195
|
SocketFunction.addGlobalClientHook(async function identityHook(context) {
|
|
194
196
|
if (context.call.classGuid === IdentityController._classGuid) return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { measureWrap } from "socket-function/src/profiling/measure";
|
|
2
2
|
import { getCommonName, getIdentityCA, getMachineId, getOwnMachineId } from "../-a-auth/certs";
|
|
3
3
|
import { getArchives } from "../-a-archives/archives";
|
|
4
|
-
import { isNode, timeInSecond } from "socket-function/src/misc";
|
|
4
|
+
import { isNode, throttleFunction, timeInSecond } from "socket-function/src/misc";
|
|
5
5
|
import { SocketFunctionHook } from "socket-function/SocketFunctionTypes";
|
|
6
6
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
7
7
|
import { IdentityController_getMachineId } from "../-c-identity/IdentityController";
|
|
@@ -10,7 +10,9 @@ import { getNodeIdDomainMaybeUndefined, getNodeIdLocation } from "socket-functio
|
|
|
10
10
|
import { trustCertificate } from "socket-function/src/certStore";
|
|
11
11
|
import { isClient, isServer } from "../config2";
|
|
12
12
|
import debugbreak from "debugbreak";
|
|
13
|
-
import { devDebugbreak, getDomain, isDevDebugbreak } from "../config";
|
|
13
|
+
import { devDebugbreak, getDomain, isDevDebugbreak, isPublic } from "../config";
|
|
14
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
15
|
+
import { runInSerial } from "socket-function/src/batching";
|
|
14
16
|
|
|
15
17
|
// Cache the untrust list, to prevent bugs from causing too many backend reads (while also allowing
|
|
16
18
|
// bad servers which make request before their trust is verified from staying broken).
|
|
@@ -41,10 +43,10 @@ export const requiresNetworkTrustHook: SocketFunctionHook = async config => {
|
|
|
41
43
|
};
|
|
42
44
|
export const assertIsNetworkTrusted = requiresNetworkTrustHook;
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
let lastArchivesTrusted: string[] | undefined;
|
|
45
47
|
let trustedCache = new Set<string>();
|
|
46
48
|
let untrustedCache = new Map<string, number>();
|
|
47
|
-
export async function isTrusted(machineId: string) {
|
|
49
|
+
export const isTrusted = runInSerial(async function isTrusted(machineId: string) {
|
|
48
50
|
// See the comment in requiresNetworkTrustHook for why clients have to trust all callers.
|
|
49
51
|
if (isClient()) return true;
|
|
50
52
|
|
|
@@ -56,15 +58,23 @@ export async function isTrusted(machineId: string) {
|
|
|
56
58
|
return false;
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
if (machineId.includes("..")) {
|
|
62
|
+
console.warn(`Invalid machineId in isTrust call: ${machineId}`);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
59
66
|
// Find is faster than get, and usually we don't need the full certificate
|
|
60
67
|
let trustedMachineIds = await archives().find("");
|
|
68
|
+
lastArchivesTrusted = trustedMachineIds.slice();
|
|
61
69
|
// Always trust ourself
|
|
62
70
|
trustedMachineIds.push(getOwnMachineId());
|
|
63
71
|
// IF developing, trust localhost. This allows us to develop without port forwards,
|
|
64
|
-
// on our services, which
|
|
72
|
+
// on our services, which INCREASES security, and prevents dev machines from being
|
|
65
73
|
// connected to by attackers (as dev machines might reveal unfinished content, or even
|
|
66
74
|
// have security vulnerabilities).
|
|
67
|
-
|
|
75
|
+
// - Don't trust this on public, as in theory an attacker MIGHT be able to connect
|
|
76
|
+
// from localhost (but not have disk read/write access)? Maybe...
|
|
77
|
+
if (!isPublic()) {
|
|
68
78
|
trustedMachineIds.push("127-0-0-1." + getDomain());
|
|
69
79
|
}
|
|
70
80
|
|
|
@@ -80,7 +90,7 @@ export async function isTrusted(machineId: string) {
|
|
|
80
90
|
} else {
|
|
81
91
|
return true;
|
|
82
92
|
}
|
|
83
|
-
}
|
|
93
|
+
});
|
|
84
94
|
|
|
85
95
|
export async function isNodeTrusted(nodeId: string) {
|
|
86
96
|
let domainName = getNodeIdDomainMaybeUndefined(nodeId);
|
|
@@ -103,12 +113,24 @@ const loadServerCert = cache(async (machineId: string) => {
|
|
|
103
113
|
const ensureWeAreTrusted = lazy(measureWrap(async () => {
|
|
104
114
|
let machineKeyCert = getIdentityCA();
|
|
105
115
|
let machineId = getCommonName(machineKeyCert.cert);
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
|
|
117
|
+
await isTrusted(machineId);
|
|
118
|
+
if (!lastArchivesTrusted?.includes(machineId)) {
|
|
108
119
|
await archives().set(machineId, machineKeyCert.cert);
|
|
109
120
|
}
|
|
110
121
|
}));
|
|
111
122
|
|
|
123
|
+
async function loadTrustCerts(nodeId: string) {
|
|
124
|
+
let location = getNodeIdLocation(nodeId);
|
|
125
|
+
if (location) {
|
|
126
|
+
let machineId = getMachineId(location.address);
|
|
127
|
+
let isTrustedBool = await isTrusted(machineId);
|
|
128
|
+
if (isTrustedBool) {
|
|
129
|
+
await loadServerCert(machineId);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
112
134
|
export const isTrustedByNode = cache(async function isTrustedByNode(nodeId: string) {
|
|
113
135
|
return await TrustedController.nodes[nodeId].isTrusted();
|
|
114
136
|
});
|
|
@@ -128,8 +150,8 @@ const TrustedController = SocketFunction.register(
|
|
|
128
150
|
);
|
|
129
151
|
|
|
130
152
|
|
|
131
|
-
if (isNode()) {
|
|
132
153
|
|
|
154
|
+
if (isNode()) {
|
|
133
155
|
// We have to be trusted if we make calls to a trusted endpoint, OR our mounting
|
|
134
156
|
// (really only if we are mounting a trusted endpoint, but we don't actually know that)
|
|
135
157
|
requiresNetworkTrustHook.clientHook = async config => {
|
|
@@ -138,13 +160,11 @@ if (isNode()) {
|
|
|
138
160
|
|
|
139
161
|
// Load the remote certificate, in the almost certain case it isn't a real certificate, and is just internal
|
|
140
162
|
SocketFunction.addGlobalClientHook(async config => {
|
|
141
|
-
await
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
}
|
|
163
|
+
await measureWrap(async function checkTrust() {
|
|
164
|
+
await Promise.all([
|
|
165
|
+
ensureWeAreTrusted(),
|
|
166
|
+
loadTrustCerts(config.call.nodeId)
|
|
167
|
+
]);
|
|
168
|
+
})();
|
|
149
169
|
});
|
|
150
170
|
}
|
|
@@ -281,10 +281,6 @@ async function getModuleFromSpecBase(
|
|
|
281
281
|
deployPath = packagePath + "deploy.ts";
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
if (path.startsWith("/root")) {
|
|
285
|
-
devDebugbreak();
|
|
286
|
-
}
|
|
287
|
-
|
|
288
284
|
console.log(blue(`require(${JSON.stringify(path)})`));
|
|
289
285
|
|
|
290
286
|
// Set functionSpec for the next synchronous evaluation
|
|
@@ -236,7 +236,7 @@ export async function baseAddCall(call: CallSpec, nodeId: string, cancel: () =>
|
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
class QuerysubControllerBase {
|
|
239
|
+
export class QuerysubControllerBase {
|
|
240
240
|
public async watch(config: WatchConfig) {
|
|
241
241
|
for (let path of config.paths) {
|
|
242
242
|
Querysub.assertDomainAllowed(path);
|
|
@@ -8,7 +8,7 @@ import { CallSpec, FunctionResult, FunctionSpec, debugCallSpec, functionSchema,
|
|
|
8
8
|
import { getModuleFromConfig, setGitURLMapping } from "../3-path-functions/pathFunctionLoader";
|
|
9
9
|
import { logErrors } from "../errors";
|
|
10
10
|
import { getParentPathStr, getPathFromStr, getPathStr1, getPathStr2 } from "../path";
|
|
11
|
-
import { Querysub, QuerysubController, baseAddCall, callWaitOn, querysubNodeId } from "./QuerysubController";
|
|
11
|
+
import { Querysub, QuerysubController, QuerysubControllerBase, baseAddCall, callWaitOn, querysubNodeId } from "./QuerysubController";
|
|
12
12
|
import { Benchmark } from "../diagnostics/benchmark";
|
|
13
13
|
import { parseArgs } from "../3-path-functions/PathFunctionHelpers";
|
|
14
14
|
import { runInSerial } from "socket-function/src/batching";
|
|
@@ -27,9 +27,18 @@ const cborEncoder = lazy(() => new cbor.Encoder({ structuredClone: true }));
|
|
|
27
27
|
// do too much for us if we already have the fully resolved path...
|
|
28
28
|
// - Although using it DOES allow permissions checks to work nicely, so, eh... maybe it is fine to use pathFunctionLoader?
|
|
29
29
|
const addModuleToLoader = cacheJSONArgsEqual(async (spec: FunctionSpec): Promise<void> => {
|
|
30
|
-
let
|
|
31
|
-
if (
|
|
32
|
-
|
|
30
|
+
let controller: QuerysubControllerBase;
|
|
31
|
+
if (isNode()) {
|
|
32
|
+
// NOTE: If we are on node, then the require WON'T run over the network, so we need to use
|
|
33
|
+
// our local module path, not the remove one.
|
|
34
|
+
controller = new QuerysubControllerBase();
|
|
35
|
+
} else {
|
|
36
|
+
let nodeId = await querysubNodeId();
|
|
37
|
+
if (!nodeId) throw new Error("No querysub node found");
|
|
38
|
+
controller = QuerysubController.nodes[nodeId] as any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let path = await controller.getModulePath({ functionSpec: spec });
|
|
33
42
|
setGitURLMapping({ spec, resolvedPath: path });
|
|
34
43
|
});
|
|
35
44
|
|
|
@@ -503,6 +512,9 @@ export async function getCallWrites(config: {
|
|
|
503
512
|
let exportPath = getPathFromStr(functionSpec.exportPathStr);
|
|
504
513
|
let exportObj = module.exports;
|
|
505
514
|
for (let path of exportPath) {
|
|
515
|
+
if (!(path in exportObj)) {
|
|
516
|
+
throw new Error(`Export not found for call prediction: ${path}, in ${call.DomainName}.${call.ModuleId}.${call.FunctionId}. Have ${Object.keys(exportObj)}`);
|
|
517
|
+
}
|
|
506
518
|
exportObj = exportObj[path];
|
|
507
519
|
}
|
|
508
520
|
let baseFunction = exportObj as Function;
|
|
@@ -4,6 +4,7 @@ import { registerMeasureInfo } from "socket-function/src/profiling/measure";
|
|
|
4
4
|
import { isNode } from "typesafecss";
|
|
5
5
|
import { monitorEventLoopDelay } from "perf_hooks";
|
|
6
6
|
import { registerNodeMetadata } from "./nodeMetadata";
|
|
7
|
+
import { getOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
|
|
7
8
|
|
|
8
9
|
const POLL_INTERVAL = 350;
|
|
9
10
|
|
|
@@ -29,6 +30,8 @@ export function trackSynchronousLag() {
|
|
|
29
30
|
return [`< ${formatTime(max / 1e6)}`, `99% < ${formatTime(p99 / 1e6)}`, `50% < ${formatTime(p50 / 1e6)}`];
|
|
30
31
|
}
|
|
31
32
|
});
|
|
33
|
+
|
|
34
|
+
registerMeasureInfo(() => getOwnNodeId());
|
|
32
35
|
} else {
|
|
33
36
|
let lastLag = POLL_INTERVAL;
|
|
34
37
|
let time = Date.now();
|
|
@@ -55,6 +55,14 @@ function getUsedHeapSize() {
|
|
|
55
55
|
return (window.performance as any).memory.usedJSHeapSize;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
function getBufferUsage() {
|
|
59
|
+
if (isNode()) {
|
|
60
|
+
let usage = process.memoryUsage();
|
|
61
|
+
return usage.arrayBuffers;
|
|
62
|
+
} else {
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
58
66
|
|
|
59
67
|
function logResourcesNow() {
|
|
60
68
|
let resourcesWithCounts = resources.map(resource => ({ ...resource, count: resource.getCount() }));
|
|
@@ -67,6 +75,7 @@ function logResourcesNow() {
|
|
|
67
75
|
}
|
|
68
76
|
} else {
|
|
69
77
|
logNodeStateStats("Heap", formatNumber)(getUsedHeapSize());
|
|
78
|
+
logNodeStateStats("Buffers", formatNumber)(getBufferUsage());
|
|
70
79
|
logNodeStateStats("All Memory", formatNumber)(getHeapSize());
|
|
71
80
|
for (let resource of resourcesWithCounts) {
|
|
72
81
|
if (resource.count === 0) continue;
|