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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.24.0",
3
+ "version": "0.26.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",
@@ -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 measureWrap(async function callChangeIdentity() {
186
- await timeoutToError(
187
- 10 * 1000,
188
- IdentityController.nodes[nodeId].changeIdentity(signature, payload),
189
- () => new Error(`Timeout calling changeIdentity for ${nodeId}`)
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 is INCREASES security, and prevents dev machines from being
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
- if (isDevDebugbreak()) {
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
- let inArchives = await archives().get(machineId);
107
- if (!inArchives) {
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 ensureWeAreTrusted();
142
- let location = getNodeIdLocation(config.call.nodeId);
143
- if (location) {
144
- let machineId = getMachineId(location.address);
145
- if (await isTrusted(machineId)) {
146
- await loadServerCert(machineId);
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 on the public ip.`));
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
- onReadyReady = (nodeId, time) => {
197
+ onReadReady = (nodeId, time) => {
198
198
  let obj = this.authorities.get(nodeId);
199
199
  if (!obj) {
200
- // HACK: Sometimes when a node is first added we can't identify it yet. I THINK this is because
201
- // we haven't learned to trust the key, or... something? Hmm...
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 onReadyReady = (nodeId: string, time: number) => { };
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
- onReadyReady(nodeIdCaller, time);
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 nodeId = await querysubNodeId();
31
- if (!nodeId) throw new Error("No querysub node found");
32
- let path = await QuerysubController.nodes[nodeId].getModulePath({ functionSpec: spec });
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;