Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing network authentication and segregation logic to the nodes domain #804

Draft
wants to merge 8 commits into
base: staging
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
291 changes: 265 additions & 26 deletions src/nodes/NodeConnectionManager.ts

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion src/nodes/NodeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import * as nodesEvents from './events';
import * as nodesErrors from './errors';
import * as agentErrors from './agent/errors';
import NodeConnectionQueue from './NodeConnectionQueue';
import { ErrorNodeManagerFindNodeFailed } from './errors';
import { assertClaimNetworkAuthority } from '../claims/payloads/claimNetworkAuthority';
import { assertClaimNetworkAccess } from '../claims/payloads/claimNetworkAccess';
import Token from '../tokens/Token';
Expand Down Expand Up @@ -643,7 +644,14 @@ class NodeManager {
try {
return await Promise.any([findBySignal, findByDirect, findByMDNS]);
} catch (e) {
// FIXME: check error type and throw if not connection related failure
if (e instanceof AggregateError) {
for (const error of e.errors) {
// Checking if each error is an expected error
if (!(error instanceof ErrorNodeManagerFindNodeFailed)) throw e;
}
} else if (!(e instanceof ErrorNodeManagerFindNodeFailed)) {
throw e;
}
return;
} finally {
abortController.abort(abortPendingConnectionsReason);
Expand Down
3 changes: 3 additions & 0 deletions src/nodes/agent/callers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import nodesAuthenticateConnection from './nodesAuthenticateConnection';
import nodesClaimsGet from './nodesClaimsGet';
import nodesClosestActiveConnectionsGet from './nodesClosestActiveConnectionsGet';
import nodesClosestLocalNodesGet from './nodesClosestLocalNodesGet';
Expand All @@ -15,6 +16,7 @@ import vaultsScan from './vaultsScan';
* Client manifest
*/
const manifestClient = {
nodesAuthenticateConnection,
nodesClaimsGet,
nodesClosestActiveConnectionsGet,
nodesClosestLocalNodesGet,
Expand All @@ -34,6 +36,7 @@ type AgentClientManifest = typeof manifestClient;
export default manifestClient;

export {
nodesAuthenticateConnection,
nodesClaimsGet,
nodesClosestActiveConnectionsGet,
nodesClosestLocalNodesGet,
Expand Down
12 changes: 12 additions & 0 deletions src/nodes/agent/callers/nodesAuthenticateConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { HandlerTypes } from '@matrixai/rpc';
import type NodesAuthenticateConnection from '../handlers/NodesAuthenticateConnection';
import { UnaryCaller } from '@matrixai/rpc';

type CallerTypes = HandlerTypes<NodesAuthenticateConnection>;

const nodesAuthenticateConnection = new UnaryCaller<
CallerTypes['input'],
CallerTypes['output']
>();

export default nodesAuthenticateConnection;
43 changes: 43 additions & 0 deletions src/nodes/agent/handlers/NodesAuthenticateConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type {
AgentRPCRequestParams,
AgentRPCResponseResult,
SuccessMessage,
} from '../types';
import type NodeConnectionManager from '../../../nodes/NodeConnectionManager';
import type { JSONValue } from '../../../types';
import type { ContextTimed } from '@matrixai/contexts';
import { UnaryHandler } from '@matrixai/rpc';
import * as agentErrors from '../errors';
import * as agentUtils from '../utils';

class NodesAuthenticateConnection extends UnaryHandler<
{
nodeConnectionManager: NodeConnectionManager;
},
AgentRPCRequestParams,
AgentRPCResponseResult<SuccessMessage>
> {
public handle = async (
_input,
_cancel,
meta: Record<string, JSONValue> | undefined,
ctx: ContextTimed,
): Promise<AgentRPCResponseResult<SuccessMessage>> => {
const { nodeConnectionManager } = this.container;
// Connections should always be validated
const requestingNodeId = agentUtils.nodeIdFromMeta(meta);
if (requestingNodeId == null) {
throw new agentErrors.ErrorAgentNodeIdMissing();
}
await nodeConnectionManager.handleReverseAuthenticate(
requestingNodeId,
ctx,
);
return {
type: 'success',
success: true,
};
};
}

export default NodesAuthenticateConnection;
3 changes: 3 additions & 0 deletions src/nodes/agent/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type NodeManager from '../../../nodes/NodeManager';
import type NodeConnectionManager from '../../../nodes/NodeConnectionManager';
import type NotificationsManager from '../../../notifications/NotificationsManager';
import type VaultManager from '../../../vaults/VaultManager';
import NodesAuthenticateConnection from './NodesAuthenticateConnection';
import NodesClaimsGet from './NodesClaimsGet';
import NodesClosestActiveConnectionsGet from './NodesClosestActiveConnectionsGet';
import NodesClosestLocalNodesGet from './NodesClosestLocalNodesGet';
Expand Down Expand Up @@ -36,6 +37,7 @@ const manifestServer = (container: {
vaultManager: VaultManager;
}) => {
return {
nodesAuthenticateConnection: new NodesAuthenticateConnection(container),
nodesClaimsGet: new NodesClaimsGet(container),
nodesClosestActiveConnectionsGet: new NodesClosestActiveConnectionsGet(
container,
Expand All @@ -57,6 +59,7 @@ type AgentServerManifest = ReturnType<typeof manifestServer>;
export default manifestServer;

export {
NodesAuthenticateConnection,
NodesClaimsGet,
NodesClosestActiveConnectionsGet,
NodesClosestLocalNodesGet,
Expand Down
23 changes: 22 additions & 1 deletion src/nodes/agent/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { ClaimIdEncoded, NodeIdEncoded, VaultIdEncoded } from '../../ids';
import type { VaultAction, VaultName } from '../../vaults/types';
import type { SignedNotification } from '../../notifications/types';
import type { Host, Hostname, Port } from '../../network/types';
import type { NodeContact } from '../../nodes/types';
import type { NetworkId, NodeContact } from '../../nodes/types';

type AgentRPCRequestParams<T extends JSONObject = JSONObject> =
JSONRPCRequestParams<T>;
Expand Down Expand Up @@ -77,6 +77,23 @@ type VaultsScanMessage = VaultInfo & {
vaultPermissions: Array<VaultAction>;
};

type SuccessMessage = {
type: 'success';
success: boolean;
};

type NodesAuthenticateConnectionMessage =
| NodesAuthenticateConnectionMessageBasicPublic
| NodesAuthenticateConnectionMessageNone;

type NodesAuthenticateConnectionMessageBasicPublic = {
type: 'NodesAuthenticateConnectionMessageBasicPublic';
networkId: NetworkId;
}
type NodesAuthenticateConnectionMessageNone = {
type: 'NodesAuthenticateConnectionMessageNone';
}

export type {
AgentRPCRequestParams,
AgentRPCResponseResult,
Expand All @@ -91,4 +108,8 @@ export type {
SignedNotificationEncoded,
VaultInfo,
VaultsScanMessage,
SuccessMessage,
NodesAuthenticateConnectionMessage,
NodesAuthenticateConnectionMessageBasicPublic,
NodesAuthenticateConnectionMessageNone,
};
12 changes: 12 additions & 0 deletions src/nodes/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { NodeId, NodeIdString, NodeIdEncoded } from '../ids/types';
import type { Host, Hostname, Port } from '../network/types';
import type { Opaque } from '../types';
import {NodesAuthenticateConnectionMessage} from "@/nodes/agent/types";
import {ContextTimed} from "@matrixai/contexts";

/**
* Key indicating which space the NodeGraph is in
Expand Down Expand Up @@ -71,6 +73,13 @@ enum ConnectionErrorReason {
ForceClose = 'NodeConnection is forcing destruction',
}

type NetworkId = string;
type AuthenticateNetworkForwardCallback = (ctx: ContextTimed) => Promise<NodesAuthenticateConnectionMessage>;
/**
* Callback should throw on authentication failure
*/
type AuthenticateNetworkReverseCallback = (message: NodesAuthenticateConnectionMessage, ctx: ContextTimed) => Promise<void>;

export type {
NodeId,
NodeIdString,
Expand All @@ -85,6 +94,9 @@ export type {
NodeBucketMeta,
NodeBucket,
NodeGraphSpace,
NetworkId,
AuthenticateNetworkForwardCallback,
AuthenticateNetworkReverseCallback,
};

export { ConnectionErrorCode, ConnectionErrorReason };
86 changes: 82 additions & 4 deletions tests/nodes/NodeConnectionManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import NodeConnectionManager from '@/nodes/NodeConnectionManager';
import NodesConnectionSignalFinal from '@/nodes/agent/handlers/NodesConnectionSignalFinal';
import NodesConnectionSignalInitial from '@/nodes/agent/handlers/NodesConnectionSignalInitial';
import * as utils from '@/utils';
import NodesAuthenticateConnection from '@/nodes/agent/handlers/NodesAuthenticateConnection';
import * as nodesTestUtils from './utils';
import * as keysTestUtils from '../keys/utils';
import * as testsUtils from '../utils';
Expand Down Expand Up @@ -91,7 +92,13 @@ describe(`${NodeConnectionManager.name}`, () => {
},
startOptions: {
host: localHost,
agentService: () => dummyManifest,
agentService: (nodeConnectionManager) => {
return {
nodesAuthenticateConnection: new NodesAuthenticateConnection({
nodeConnectionManager,
}),
} as AgentServerManifest;
},
},
logger: logger.getChild(`${NodeConnectionManager.name}Local`),
});
Expand All @@ -103,12 +110,20 @@ describe(`${NodeConnectionManager.name}`, () => {
},
startOptions: {
host: localHost,
agentService: () => dummyManifest,
agentService: (nodeConnectionManager) => {
return {
nodesAuthenticateConnection: new NodesAuthenticateConnection({
nodeConnectionManager,
}),
} as AgentServerManifest;
},
},
logger: logger.getChild(`${NodeConnectionManager.name}Peer1`),
});
logger.warn('----------------------------------------------');
});
afterEach(async () => {
logger.warn('----------------------------------------------');
await ncmLocal.nodeConnectionManager.stop({ force: true });
await ncmPeer1.nodeConnectionManager.stop({ force: true });
});
Expand Down Expand Up @@ -319,6 +334,7 @@ describe(`${NodeConnectionManager.name}`, () => {
const connectionAndTimer = ncmLocal.nodeConnectionManager.getConnection(
ncmPeer1.nodeId,
);
await ncmLocal.nodeConnectionManager.isAuthenticatedP(ncmPeer1.nodeId);
await ncmLocal.nodeConnectionManager.withConnF(
ncmPeer1.nodeId,
async () => {
Expand Down Expand Up @@ -524,6 +540,7 @@ describe(`${NodeConnectionManager.name}`, () => {
localHost,
ncmPeer1.port,
);
await ncmLocal.nodeConnectionManager.isAuthenticatedP(ncmPeer1.nodeId);
// Wait for timeout.
await ncmLocal.nodeConnectionManager.withConnF(
ncmPeer1.nodeId,
Expand Down Expand Up @@ -560,7 +577,6 @@ describe(`${NodeConnectionManager.name}`, () => {
expect(connection.address.port).toBe(
ncmPeer1.nodeConnectionManager.port,
);
expect(connection.usageCount).toBe(0);
}
});
test('stopping NodeConnectionManager should destroy all connections', async () => {
Expand Down Expand Up @@ -645,6 +661,42 @@ describe(`${NodeConnectionManager.name}`, () => {
ncmLocal.nodeConnectionManager.hasConnection(ncmPeer1.nodeId),
).toBeFalse();
});

// TODO: This is a temp test
test('can authenticate a connection', async () => {
await ncmLocal.nodeConnectionManager.createConnection(
[ncmPeer1.nodeId],
localHost,
ncmPeer1.port,
);
// Should exist in the map now.
expect(
ncmLocal.nodeConnectionManager.hasConnection(ncmPeer1.nodeId),
).toBeTrue();
expect(
ncmLocal.nodeConnectionManager.isAuthenticated(ncmPeer1.nodeId),
).toBeFalse();
await ncmLocal.nodeConnectionManager.isAuthenticatedP(ncmPeer1.nodeId);
expect(
ncmLocal.nodeConnectionManager.isAuthenticated(ncmPeer1.nodeId),
).toBeTrue();
});
test('can fail authentication', async () => {
await ncmLocal.nodeConnectionManager.createConnection(
[ncmPeer1.nodeId],
localHost,
ncmPeer1.port,
);
// Should exist in the map now.
expect(
ncmLocal.nodeConnectionManager.isAuthenticated(ncmPeer1.nodeId),
).toBeFalse();
await expect(ncmLocal.nodeConnectionManager.isAuthenticatedP(ncmPeer1.nodeId)).rejects.not.toThrow();
expect(
ncmLocal.nodeConnectionManager.isAuthenticated(ncmPeer1.nodeId),
).toBeFalse();
});
test.todo('Cant make most RPC calls while unauthenticated');
});
describe('With 2 peers', () => {
let ncmLocal: NCMState;
Expand All @@ -659,7 +711,13 @@ describe(`${NodeConnectionManager.name}`, () => {
},
startOptions: {
host: localHost,
agentService: () => dummyManifest,
agentService: (nodeConnectionManager) => {
return {
nodesAuthenticateConnection: new NodesAuthenticateConnection({
nodeConnectionManager,
}),
} as AgentServerManifest;
},
},
logger: logger.getChild(`${NodeConnectionManager.name}Local`),
});
Expand All @@ -673,6 +731,9 @@ describe(`${NodeConnectionManager.name}`, () => {
host: localHost,
agentService: (nodeConnectionManager) =>
({
nodesAuthenticateConnection: new NodesAuthenticateConnection({
nodeConnectionManager,
}),
nodesConnectionSignalFinal: new NodesConnectionSignalFinal({
nodeConnectionManager,
logger,
Expand All @@ -694,6 +755,9 @@ describe(`${NodeConnectionManager.name}`, () => {
host: localHost,
agentService: (nodeConnectionManager) =>
({
nodesAuthenticateConnection: new NodesAuthenticateConnection({
nodeConnectionManager,
}),
nodesConnectionSignalFinal: new NodesConnectionSignalFinal({
nodeConnectionManager,
logger,
Expand Down Expand Up @@ -894,3 +958,17 @@ describe(`${NodeConnectionManager.name}`, () => {
});
});
});

test('asd', async () => {
const timer = new Timer({
delay: 1000,
});
console.log('waiting');
timer.cancel(Error('boi'));
timer.catch(() => {});
await timer.then(
() => {},
() => {},
);
console.log('done');
});