querysub 0.24.0 → 0.26.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 +1 -1
- package/src/-c-identity/IdentityController.ts +9 -7
- package/src/-d-trust/NetworkTrust2.ts +38 -18
- package/src/-e-certs/EdgeCertController.ts +1 -1
- package/src/0-path-value-core/NodePathAuthorities.ts +5 -6
- 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/NodeViewer.tsx +1 -1
- package/src/diagnostics/trackResources.ts +9 -0
package/package.json
CHANGED
|
@@ -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
|
}
|
|
@@ -169,7 +169,7 @@ export async function publishMachineARecords() {
|
|
|
169
169
|
// and not set public (such as the FunctionRunner). SO... just ignore this, leaving the records as
|
|
170
170
|
// public, even though the current run doesn't have public set.
|
|
171
171
|
if (process.argv[1].includes("server.ts")) {
|
|
172
|
-
console.log(yellow(`Current process is not marked as public, but machine previous had public services. NOT publishing A records to point to 127.0.0.1, which will make service inaccessible if the port is not forwarded open
|
|
172
|
+
console.log(yellow(`Current process is not marked as public, but machine previous had public services. NOT publishing A records to point to 127.0.0.1, which will make service inaccessible if the port is not port forwarded (or open). I recommend using noproxy.DOMAIN.com to access the server. 127-0-0-1.DOMAIN.com might work as well.`));
|
|
173
173
|
}
|
|
174
174
|
return;
|
|
175
175
|
}
|
|
@@ -194,12 +194,11 @@ class NodePathAuthorities {
|
|
|
194
194
|
private async watchAuthorityPaths() {
|
|
195
195
|
await onNodeDiscoveryReady();
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
onReadReady = (nodeId, time) => {
|
|
198
198
|
let obj = this.authorities.get(nodeId);
|
|
199
199
|
if (!obj) {
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
// ingestNewNodeIds([nodeId], []);
|
|
200
|
+
// Might as well use this to add the node, if we don't know about it yet.
|
|
201
|
+
ingestNewNodeIds([nodeId], []);
|
|
203
202
|
return;
|
|
204
203
|
}
|
|
205
204
|
obj.isReadReady = time;
|
|
@@ -936,7 +935,7 @@ function authoritiesMightOverlap(other: AuthorityPath, current: AuthorityPath):
|
|
|
936
935
|
|
|
937
936
|
|
|
938
937
|
|
|
939
|
-
let
|
|
938
|
+
let onReadReady = (nodeId: string, time: number) => { };
|
|
940
939
|
class PathControllerBase {
|
|
941
940
|
public async getAuthorityPaths() {
|
|
942
941
|
return pathValueAuthority2.getSelfAuthorities();
|
|
@@ -953,7 +952,7 @@ class PathControllerBase {
|
|
|
953
952
|
public async broadcastReadReady(time: number) {
|
|
954
953
|
let nodeIdCaller = IdentityController_getCurrentReconnectNodeIdAssert();
|
|
955
954
|
console.log(magenta(`Received ready broadcast`), { nodeIdCaller });
|
|
956
|
-
|
|
955
|
+
onReadReady(nodeIdCaller, time);
|
|
957
956
|
}
|
|
958
957
|
}
|
|
959
958
|
const PathController = SocketFunction.register(
|
|
@@ -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();
|
|
@@ -185,7 +185,7 @@ export class NodeViewer extends qreact.Component {
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
let builtinGroups = {
|
|
188
|
-
"Default": ["buttons", "devToolsURL", "nodeId", "ip", "uptime", "loadTime", "Heap", "All Memory", "Blocking Lag", "port", "threadId", "machineId", "apiError", "live_entryPoint"],
|
|
188
|
+
"Default": ["buttons", "devToolsURL", "nodeId", "ip", "uptime", "loadTime", "Heap", "Buffers", "All Memory", "Blocking Lag", "port", "threadId", "machineId", "apiError", "live_entryPoint"],
|
|
189
189
|
};
|
|
190
190
|
// Column => group
|
|
191
191
|
let builtInGroupsLookup = new Map<string, string>();
|
|
@@ -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;
|