@serve.zone/interfaces 32.5.0 → 32.6.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/changelog.md +38 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/cluster.d.ts +4 -5
- package/dist_ts/data/cluster.js +4 -13
- package/dist_ts/data/clustervpn.d.ts +11 -0
- package/dist_ts/data/clustervpn.js +18 -7
- package/dist_ts/data/networkaddressplan.d.ts +21 -15
- package/dist_ts/data/networkaddressplan.js +27 -20
- package/dist_ts/data/networkcredential.d.ts +0 -7
- package/dist_ts/data/networkcredential.js +5 -22
- package/package.json +1 -1
- package/readme.md +32 -24
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/cluster.ts +7 -20
- package/ts/data/clustervpn.ts +27 -8
- package/ts/data/networkaddressplan.ts +32 -25
- package/ts/data/networkcredential.ts +4 -29
package/changelog.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026-09-19 - 32.6.0
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- `data.IClusterVpnNetwork` now carries `controlPrefix`, the subnet the cluster's hub serves its
|
|
8
|
+
control plane on. A managed hub fixes that subnet when it binds and admits a member's control
|
|
9
|
+
address only inside it, and no other Cloudly-to-relay carrier hands a relay the address plan, so
|
|
10
|
+
a relay could name the authority its hub is bound to but not the subnet it has to bind — leaving
|
|
11
|
+
it to infer one from the members it was sent or to keep a copy of Cloudly's plan, both of which
|
|
12
|
+
make a hub disagree with the plan it serves. `snapshotClusterVpnNetwork` reads the member with
|
|
13
|
+
the new `data.runtimeNetworkVpnControlPrefix(value)` — a canonical private prefix no longer than
|
|
14
|
+
`runtimeNetworkAddressPlanContract.maximumControlPrefixLength`, the same reader the address plan
|
|
15
|
+
uses for `vpn.controlPrefix` — and additionally requires every `node.controlAddress` to lie
|
|
16
|
+
inside it and every `workloadPrefixes[].cidr` to stay clear of it, the two rules the hub enforces
|
|
17
|
+
anyway. A network that the hub could only refuse is now refused while Cloudly compiles it. No
|
|
18
|
+
refusal name was added: `admitRuntimeNetworkAddressPlanChange` refuses every VPN change with
|
|
19
|
+
`plan-vpn-immutable`, so one runtime network's control prefix never moves, and a network naming
|
|
20
|
+
another prefix is another authority, which `vpn-network-authority-foreign` already decides.
|
|
21
|
+
A stored network without the member is refused by the closed record, exactly like any other
|
|
22
|
+
missing member; Cloudly compiles the prefix from the record that already holds it. A pushed
|
|
23
|
+
network that keeps its authority but names another control prefix is refused as
|
|
24
|
+
`vpn-network-authority-foreign` too: the hub is bound to one subnet for its lifetime.
|
|
25
|
+
- This minor removes rather than deprecates the WebSocket half of the hub contract. The managed VPN hub is QUIC-only, and the contract now states only what a hub can serve. A
|
|
26
|
+
smartvpn managed hub accepts plain streams on its WebSocket listener and consumes its TLS
|
|
27
|
+
material on the QUIC listener alone, so a `wss://` managed session ends in a TLS handshake EOF
|
|
28
|
+
and the TLS name such a session would be dialled under is unsatisfiable. Removed, under the
|
|
29
|
+
one-common-major rule and with no shipped consumer of the 32.4.0 hub shapes:
|
|
30
|
+
`runtimeNetworkAddressPlanContract.hubTransports` loses its `websocket` row, so
|
|
31
|
+
`TRuntimeNetworkVpnTransport` (kept, and the place a second transport returns once smartvpn
|
|
32
|
+
serves one) is `'quic'` alone and `runtimeNetworkVpnHubEndpoints(hub)` yields the QUIC endpoint
|
|
33
|
+
only; `IRuntimeNetworkVpnHub` and `IClusterRelayVpnHub` lose `websocketPort` and state a bound
|
|
34
|
+
`quicPort: number` instead of a nullable port per transport, so `validateClusterRelayVpnHub` no
|
|
35
|
+
longer answers `must bind at least one of quicPort and websocketPort` and refuses a null or
|
|
36
|
+
absent `quicPort` by that member's own name; and
|
|
37
|
+
`IGetRuntimeManagedVpnCredentialResponse.serverName` is gone, with the "present exactly when the
|
|
38
|
+
transport is websocket" rule it carried. One hub now costs one of an authority's 64 platform
|
|
39
|
+
endpoints, so 64 clusters fit one authority where 32 did.
|
|
40
|
+
|
|
3
41
|
## 2026-09-19 - 32.5.0
|
|
4
42
|
|
|
5
43
|
### Fixes
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@serve.zone/interfaces',
|
|
6
|
-
version: '32.
|
|
6
|
+
version: '32.6.0',
|
|
7
7
|
description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLHVGQUF1RjtDQUNyRyxDQUFBIn0=
|
|
@@ -54,17 +54,16 @@ export interface IClusterRelay {
|
|
|
54
54
|
* The relay is its cluster's packet relay: every node of the cluster dials this hub and no
|
|
55
55
|
* inter-node packet leaves the cluster. It reports what it bound, exactly like `listenAddress` and
|
|
56
56
|
* `listenPort`, and Cloudly composes the platform endpoints from it — a relay names no endpoint id
|
|
57
|
-
*
|
|
57
|
+
* of its own. `publicKey` is the hub's Noise public key in the one canonical
|
|
58
58
|
* 32-byte base64 encoding every Noise key of this contract is read with; it is public material and
|
|
59
|
-
* authenticates nothing by itself.
|
|
60
|
-
*
|
|
59
|
+
* authenticates nothing by itself. `quicPort` is the port it bound for the one transport a managed
|
|
60
|
+
* hub serves, so a relay that advertises a hub advertises a reachable one.
|
|
61
61
|
*/
|
|
62
62
|
export interface IClusterRelayVpnHub {
|
|
63
63
|
publicKey: string;
|
|
64
64
|
/** The address the hub bound, as an IPv4 literal inside the address plan's `vpn.hubPrefix`. */
|
|
65
65
|
address: string;
|
|
66
|
-
quicPort: number
|
|
67
|
-
websocketPort: number | null;
|
|
66
|
+
quicPort: number;
|
|
68
67
|
}
|
|
69
68
|
/**
|
|
70
69
|
* What a cluster relay tells Cloudly about itself once its socket is authenticated.
|
package/dist_ts/data/cluster.js
CHANGED
|
@@ -112,10 +112,6 @@ const validateClusterRelayVpnKey = (valueArg, pathArg) => {
|
|
|
112
112
|
return [`${pathArg} must be a canonical 32-byte base64 public key`];
|
|
113
113
|
}
|
|
114
114
|
};
|
|
115
|
-
/** A port the hub bound, or `null` for a transport it did not bind. */
|
|
116
|
-
const validateClusterRelayHubPort = (valueArg, pathArg) => (valueArg === null || validateClusterRelayListenPort(valueArg, pathArg).length === 0
|
|
117
|
-
? []
|
|
118
|
-
: [`${pathArg} must be null or an integer from 1 to 65535`]);
|
|
119
115
|
/**
|
|
120
116
|
* Validates one hub advertisement and names every reason it is refused.
|
|
121
117
|
*
|
|
@@ -124,19 +120,14 @@ const validateClusterRelayHubPort = (valueArg, pathArg) => (valueArg === null ||
|
|
|
124
120
|
*/
|
|
125
121
|
export const validateClusterRelayVpnHub = (valueArg, pathArg = 'cluster relay vpn hub') => {
|
|
126
122
|
if (!isRecord(valueArg)
|
|
127
|
-
|| !hasExactKeys(valueArg, ['publicKey', 'address', 'quicPort'
|
|
123
|
+
|| !hasExactKeys(valueArg, ['publicKey', 'address', 'quicPort'])) {
|
|
128
124
|
return [`${pathArg} must use its exact schema`];
|
|
129
125
|
}
|
|
130
|
-
|
|
126
|
+
return [
|
|
131
127
|
...validateClusterRelayVpnKey(valueArg.publicKey, `${pathArg} publicKey`),
|
|
132
128
|
...validateClusterRelayListenAddress(valueArg.address, `${pathArg} address`),
|
|
133
|
-
...
|
|
134
|
-
...validateClusterRelayHubPort(valueArg.websocketPort, `${pathArg} websocketPort`),
|
|
129
|
+
...validateClusterRelayListenPort(valueArg.quicPort, `${pathArg} quicPort`),
|
|
135
130
|
];
|
|
136
|
-
if (valueArg.quicPort === null && valueArg.websocketPort === null) {
|
|
137
|
-
errors.push(`${pathArg} must bind at least one of quicPort and websocketPort`);
|
|
138
|
-
}
|
|
139
|
-
return errors;
|
|
140
131
|
};
|
|
141
132
|
const validateClusterRelayEndpoint = (valueArg, pathArg) => {
|
|
142
133
|
if (!isRecord(valueArg)
|
|
@@ -215,4 +206,4 @@ export const clusterRelayCertificateDomainName = (relayArg) => {
|
|
|
215
206
|
}
|
|
216
207
|
return new URL(relayArg.origin).hostname;
|
|
217
208
|
};
|
|
218
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
209
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2x1c3Rlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvY2x1c3Rlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUV6QyxPQUFPLEVBQTRCLE1BQU0sbUJBQW1CLENBQUM7QUFFN0QsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDNUQsT0FBTyxFQUFFLHVCQUF1QixFQUF1QixNQUFNLGdCQUFnQixDQUFDO0FBQzlFLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUM1RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDL0QsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBcUloRTs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sQ0FBQyxNQUFNLGdDQUFnQyxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDNUQsK0VBQStFO0lBQy9FLDJCQUEyQjtJQUMzQiwyRkFBMkY7SUFDM0Ysb0JBQW9CO0lBQ3BCLGtHQUFrRztJQUNsRyw4QkFBOEI7Q0FDdEIsQ0FBQyxDQUFDO0FBS1osTUFBTSwrQkFBK0IsR0FBRyxJQUFJLENBQUM7QUFDN0MsTUFBTSxnQ0FBZ0MsR0FBRyxFQUFFLENBQUM7QUFFNUMsTUFBTSxRQUFRLEdBQUcsQ0FBQyxRQUFpQixFQUF1QyxFQUFFLENBQUMsQ0FDM0UsT0FBTyxDQUFDLFFBQVEsQ0FBQztPQUNkLE9BQU8sUUFBUSxLQUFLLFFBQVE7T0FDNUIsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQztPQUN4QixDQUFDLE1BQU0sQ0FBQyxjQUFjLENBQUMsUUFBUSxDQUFDLEtBQUssTUFBTSxDQUFDLFNBQVM7V0FDbkQsTUFBTSxDQUFDLGNBQWMsQ0FBQyxRQUFRLENBQUMsS0FBSyxJQUFJLENBQUMsQ0FDL0MsQ0FBQztBQUVGLE1B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
|
|
@@ -33,9 +33,16 @@ export interface IClusterVpnNetworkGrant {
|
|
|
33
33
|
* a grant that is missing is withdrawn. `revision` is Cloudly's own counter for this authority and
|
|
34
34
|
* only ever rises; `authorityId` names the authority the hub is bound to, so a network compiled for
|
|
35
35
|
* another cluster is refused rather than applied.
|
|
36
|
+
*
|
|
37
|
+
* `controlPrefix` is the subnet the hub's control plane serves: the address plan's
|
|
38
|
+
* `vpn.controlPrefix`, which the plan contract never lets change, stated here because a hub fixes
|
|
39
|
+
* its control subnet when it binds and no other carrier hands a relay the plan. Without it a relay
|
|
40
|
+
* could only infer a subnet from the members it was sent, which is how a hub comes to disagree with
|
|
41
|
+
* the plan it serves.
|
|
36
42
|
*/
|
|
37
43
|
export interface IClusterVpnNetwork {
|
|
38
44
|
authorityId: string;
|
|
45
|
+
controlPrefix: string;
|
|
39
46
|
revision: number;
|
|
40
47
|
nodes: IClusterVpnNetworkNode[];
|
|
41
48
|
grants: IClusterVpnNetworkGrant[];
|
|
@@ -107,6 +114,10 @@ export declare const clusterVpnWorkloadPolicyDomain: (digestValue: unknown) => s
|
|
|
107
114
|
* workload domain this network itself carries, on two different nodes. That last rule is what a
|
|
108
115
|
* per-cluster hub means: a grant whose other end sits on another cluster's hub cannot be expressed
|
|
109
116
|
* here at all, which is why Cloudly refuses it by name while it compiles instead of pushing it.
|
|
117
|
+
*
|
|
118
|
+
* Every control address lies inside `controlPrefix` and no workload prefix meets it — the two rules
|
|
119
|
+
* a hub enforces against the subnet it bound. Checking them while the network is compiled turns a
|
|
120
|
+
* push the hub could only refuse into a network Cloudly never states.
|
|
110
121
|
*/
|
|
111
122
|
export declare const snapshotClusterVpnNetwork: (value: unknown) => IClusterVpnNetwork;
|
|
112
123
|
export type TClusterVpnNetworkAdmission = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runtimeNetworkVpnControlPrefix } from './networkaddressplan.js';
|
|
2
|
+
import { captureNetwork, failNetwork, networkCanonical, networkContains, networkCounter, networkEqual, networkId, networkIpv4, networkList, networkNodeId, networkNoiseKey, networkOverlaps, networkPrivatePrefix, networkRecord, networkSafe, networkSorted, requireNetwork } from '../private/networkcontract.js';
|
|
2
3
|
export const clusterVpnNetworkContract = Object.freeze({
|
|
3
4
|
/** The policy domain of a node's control address; the hub relays no grant between control domains. */
|
|
4
5
|
controlPolicyDomainPrefix: 'control:',
|
|
@@ -74,21 +75,28 @@ const grantKey = (grant) => `${grant.sourceDomain}\0${grant.destinationDomain}`;
|
|
|
74
75
|
* workload domain this network itself carries, on two different nodes. That last rule is what a
|
|
75
76
|
* per-cluster hub means: a grant whose other end sits on another cluster's hub cannot be expressed
|
|
76
77
|
* here at all, which is why Cloudly refuses it by name while it compiles instead of pushing it.
|
|
78
|
+
*
|
|
79
|
+
* Every control address lies inside `controlPrefix` and no workload prefix meets it — the two rules
|
|
80
|
+
* a hub enforces against the subnet it bound. Checking them while the network is compiled turns a
|
|
81
|
+
* push the hub could only refuse into a network Cloudly never states.
|
|
77
82
|
*/
|
|
78
83
|
export const snapshotClusterVpnNetwork = (value) => networkSafe(() => {
|
|
79
84
|
const captured = captureNetwork(value);
|
|
80
85
|
requireNetwork(new TextEncoder().encode(networkCanonical(captured)).length <= clusterVpnNetworkContract.maximumCanonicalBytes);
|
|
81
|
-
const item = networkRecord(captured, ['authorityId', 'revision', 'nodes', 'grants']);
|
|
86
|
+
const item = networkRecord(captured, ['authorityId', 'controlPrefix', 'revision', 'nodes', 'grants']);
|
|
87
|
+
const controlPrefix = runtimeNetworkVpnControlPrefix(item.controlPrefix);
|
|
82
88
|
const nodes = networkSorted(networkList(item.nodes, clusterVpnNetworkContract.maximumNodes, nodeOf), node => node.nodeId);
|
|
83
89
|
const grants = networkSorted(networkList(item.grants, clusterVpnNetworkContract.maximumGrants, grantOf), grantKey);
|
|
84
90
|
const domainOwners = new Map();
|
|
85
91
|
const addresses = new Set(), keys = new Set(), cidrs = new Set();
|
|
86
92
|
for (const node of nodes) {
|
|
87
|
-
requireNetwork(!keys.has(node.publicKey) && !addresses.has(node.controlAddress)
|
|
93
|
+
requireNetwork(!keys.has(node.publicKey) && !addresses.has(node.controlAddress)
|
|
94
|
+
&& networkContains(controlPrefix, `${node.controlAddress}/32`));
|
|
88
95
|
keys.add(node.publicKey);
|
|
89
96
|
addresses.add(node.controlAddress);
|
|
90
97
|
for (const prefix of node.workloadPrefixes) {
|
|
91
|
-
requireNetwork(!cidrs.has(prefix.cidr) && !domainOwners.has(prefix.policyDomain)
|
|
98
|
+
requireNetwork(!cidrs.has(prefix.cidr) && !domainOwners.has(prefix.policyDomain)
|
|
99
|
+
&& !networkOverlaps(controlPrefix, prefix.cidr));
|
|
92
100
|
cidrs.add(prefix.cidr);
|
|
93
101
|
domainOwners.set(prefix.policyDomain, node.nodeId);
|
|
94
102
|
}
|
|
@@ -97,7 +105,7 @@ export const snapshotClusterVpnNetwork = (value) => networkSafe(() => {
|
|
|
97
105
|
const source = domainOwners.get(grant.sourceDomain), destination = domainOwners.get(grant.destinationDomain);
|
|
98
106
|
requireNetwork(source && destination && source !== destination);
|
|
99
107
|
}
|
|
100
|
-
return { authorityId: networkId(item.authorityId), revision: networkCounter(item.revision), nodes, grants };
|
|
108
|
+
return { authorityId: networkId(item.authorityId), controlPrefix, revision: networkCounter(item.revision), nodes, grants };
|
|
101
109
|
});
|
|
102
110
|
/**
|
|
103
111
|
* The pure decision a relay makes about a pushed network against the one its hub holds, or null
|
|
@@ -110,8 +118,11 @@ export const admitClusterVpnNetwork = (heldValue, nextValue) => networkSafe(() =
|
|
|
110
118
|
if (heldValue === null)
|
|
111
119
|
return { status: 'accepted' };
|
|
112
120
|
const held = snapshotClusterVpnNetwork(heldValue);
|
|
113
|
-
|
|
121
|
+
// A hub binds one control subnet for its lifetime, so a network that moves the prefix under the
|
|
122
|
+
// same authority is as foreign to this hub as one that names another authority.
|
|
123
|
+
if (held.authorityId !== next.authorityId || held.controlPrefix !== next.controlPrefix) {
|
|
114
124
|
return { status: 'refused', reason: 'vpn-network-authority-foreign' };
|
|
125
|
+
}
|
|
115
126
|
if (next.revision > held.revision)
|
|
116
127
|
return { status: 'accepted' };
|
|
117
128
|
return networkEqual(held, next) ? { status: 'replay' } : { status: 'refused', reason: 'vpn-network-revision-stale' };
|
|
@@ -144,4 +155,4 @@ export const snapshotGetClusterVpnNetworkResponse = (value) => networkSafe(() =>
|
|
|
144
155
|
const item = networkRecord(captureNetwork(value), ['network']);
|
|
145
156
|
return { network: item.network === null ? null : snapshotClusterVpnNetwork(item.network) };
|
|
146
157
|
});
|
|
147
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
158
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2x1c3RlcnZwbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvY2x1c3RlcnZwbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsOEJBQThCLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN6RSxPQUFPLEVBQUUsY0FBYyxFQUFFLFdBQVcsRUFBRSxnQkFBZ0IsRUFBRSxlQUFlLEVBQUUsY0FBYyxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsV0FBVyxFQUMzSCxXQUFXLEVBQUUsYUFBYSxFQUFFLGVBQWUsRUFBRSxlQUFlLEVBQUUsb0JBQW9CLEVBQUUsYUFBYSxFQUFFLFdBQVcsRUFDOUcsYUFBYSxFQUFFLGNBQWMsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBdUR2RSxNQUFNLENBQUMsTUFBTSx5QkFBeUIsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ3JELHNHQUFzRztJQUN0Ryx5QkFBeUIsRUFBRSxVQUFVO0lBQ3JDLDZFQUE2RTtJQUM3RSwwQkFBMEIsRUFBRSxXQUFXO0lBQ3ZDLFlBQVksRUFBRSxHQUFHO0lBQ2pCLHlFQUF5RTtJQUN6RSx1QkFBdUIsRUFBRSxHQUFHO0lBQzVCLGFBQWEsRUFBRSxJQUFJO0lBQ25CLHFCQUFxQixFQUFFLE9BQU87SUFDOUIsWUFBWSxFQUFFLCtCQUErQjtJQUM3QyxVQUFVLEVBQUUsMkNBQTJDO0NBQy9DLENBQUMsQ0FBQztBQUVaOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLENBQUMsTUFBTSw4QkFBOEIsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQzFELHVGQUF1RjtJQUN2RiwrQkFBK0I7SUFDL0IsMEZBQTBGO0lBQzFGLDRCQUE0QjtDQUNwQixDQUFDLENBQUM7QUFLWjs7Ozs7O0dBTUc7QUFDSCxNQUFNLENBQUMsTUFBTSxrQ0FBa0MsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQzlELGtGQUFrRjtJQUNsRixzQkFBc0I7SUFDdEIsNEZBQTRGO0lBQzVGLGlDQUFpQztDQUN6QixDQUFDLENBQUM7QUFnQ1osb0dBQW9HO0FBQ3BHLE1BQU0sQ0FBQyxNQUFNLDZCQUE2QixHQUFHLENBQUMsWUFBcUIsRUFBVSxFQUFFLENBQUMsV0FBVyxDQUFDLEdBQUcsRUFBRSxDQUMvRixHQUFHLHlCQUF5QixDQUFDLHlCQUF5QixHQUFHLFdBQVcsQ0FBQyxZQUFZLENBQUMsRUFBRSxDQUFDLENBQUM7QUFFeEYsbUZBQW1GO0FBQ25GLE1BQU0sQ0FBQyxNQUFNLDhCQUE4QixHQUFHLENBQUMsV0FBb0IsRUFBVSxFQUFFLENBQUMsV0FBVyxDQUFDLEdBQUcsRUFBRTtJQUMvRixjQUFjLENBQUMsT0FBTyxXQUFXLEtBQUssUUFBUSxJQUFJLHVCQUF1QixDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDO0lBQzdGLE9BQU8sR0FBRyx5QkFBeUIsQ0FBQywwQkFBMEIsR0FBSSxXQUFzQixDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0FBQ3RHLENBQUMsQ0FBQyxDQUFDO0FBRUgsTUFBTSxvQkFBb0IsR0FBRyxDQUFDLEtBQWMsRUFBVSxFQUFFO0lBQ3RELGNBQWMsQ0FBQyxPQUFPLEtBQUssS0FBSyxRQUFRO1dBQ25DLEtBQUssQ0FBQyxVQUFVLENBQUMseUJBQXlCLENBQUMsMEJBQTBCLENBQUM7V0FDdEUsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMseUJBQXlCLENBQUMsMEJBQTBCLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3RHLE9BQU8sS0FBZSxDQUFDO0FBQ3pCLENBQUMsQ0FBQztBQUVGLE1BQU0sUUFBUSxHQUFHLENBQUMsS0FBYyxFQUE0QixFQUFFO0lBQzVELE1BQU0sSUFBSSxHQUFHLGFBQWEsQ0FBQyxLQUFLLEVBQUUsQ0FBQyxNQUFNLEVBQUUsY0FBYyxDQUFDLENBQUMsQ0FBQztJQUM1RCxPQUFPLEVBQUUsSUFBSSxFQUFFLG9CQUFvQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxZQUFZLEVBQUUsb0JBQW9CLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUM7QUFDMUcsQ0FBQyxDQUFDO0FBRUYsTUFBTSxNQUFNLEdBQUcsQ0FBQyxLQUFjLEVBQTBCLEVBQUU7SUFDeEQsTUFBTSxJQUFJLEdBQUcsYUFBYSxDQUFDLEtBQUssRUFBRSxDQUFDLFFBQVEsRUFBRSxXQUFXLEVBQUUsZ0JBQWdCLEVBQUUsV0FBVyxFQUFFLGtCQUFrQixDQUFDLENBQUMsQ0FBQztJQUM5RyxPQUFPLEVBQUUsTUFBTSxFQUFFLGFBQWEsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsU0FBUyxFQUFFLGVBQWUsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsY0FBYyxFQUFFLFdBQVcsQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDO1FBQ3ZJLFNBQVMsRUFBRSxjQUFjLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQztRQUN6QyxnQkFBZ0IsRUFBRSxhQUFhLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSx5QkFBeUIsQ0FBQyx1QkFBdUIsRUFBRSxRQUFRLENBQUMsRUFDN0gsTUFBTSxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztBQUMvQixDQUFDLENBQUM7QUFFRixNQUFNLE9BQU8sR0FBRyxDQUFDLEtBQWMsRUFBMkIsRUFBRTtJQUMxRCxNQUFNLElBQUksR0FBRyxhQUFhLENBQUMsS0FBSyxFQUFFLENBQUMsY0FBYyxFQUFFLG1CQUFtQixDQUFDLENBQUMsQ0FBQztJQUN6RSxPQUFPLEVBQUUsWUFBWSxFQUFFLG9CQUFvQixDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsRUFBRSxpQkFBaUIsRUFBRSxvQkFBb0IsQ0FBQyxJQUFJLENBQUMsaUJBQWlCLENBQUMsRUFBRSxDQUFDO0FBQ3BJLENBQUMsQ0FBQztBQUVGLE1BQU0sUUFBUSxHQUFHLENBQUMsS0FBOEIsRUFBVSxFQUFFLENBQUMsR0FBRyxLQUFLLENBQUMsWUFBWSxLQUFLLEtBQUssQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO0FBRWpIOzs7Ozs7Ozs7OztHQVdHO0FBQ0gsTUFBTSxDQUFDLE1BQU0seUJBQXlCLEdBQUcsQ0FBQyxLQUFjLEVBQXNCLEVBQUUsQ0FBQyxXQUFXLENBQUMsR0FBRyxFQUFFO0lBQ2hHLE1BQU0sUUFBUSxHQUFHLGNBQWMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUN2QyxjQUFjLENBQUMsSUFBSSxXQUFXLEVBQUUsQ0FBQyxNQUFNLENBQUMsZ0JBQWdCLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxNQUFNLElBQUkseUJBQXlCLENBQUMscUJBQXFCLENBQUMsQ0FBQztJQUMvSCxNQUFNLElBQUksR0FBRyxhQUFhLENBQUMsUUFBUSxFQUFFLENBQUMsYUFBYSxFQUFFLGVBQWUsRUFBRSxVQUFVLEVBQUUsT0FBTyxFQUFFLFFBQVEsQ0FBQyxDQUFDLENBQUM7SUFDdEcsTUFBTSxhQUFhLEdBQUcsOEJBQThCLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxDQUFDO0lBQ3pFLE1BQU0sS0FBSyxHQUFHLGFBQWEsQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLEtBQUssRUFBRSx5QkFBeUIsQ0FBQyxZQUFZLEVBQUUsTUFBTSxDQUFDLEVBQUUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDMUgsTUFBTSxNQUFNLEdBQUcsYUFBYSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLHlCQUF5QixDQUFDLGFBQWEsRUFBRSxPQUFPLENBQUMsRUFBRSxRQUFRLENBQUMsQ0FBQztJQUNuSCxNQUFNLFlBQVksR0FBRyxJQUFJLEdBQUcsRUFBa0IsQ0FBQztJQUMvQyxNQUFNLFNBQVMsR0FBRyxJQUFJLEdBQUcsRUFBVSxFQUFFLElBQUksR0FBRyxJQUFJLEdBQUcsRUFBVSxFQUFFLEtBQUssR0FBRyxJQUFJLEdBQUcsRUFBVSxDQUFDO0lBQ3pGLEtBQUssTUFBTSxJQUFJLElBQUksS0FBSyxFQUFFLENBQUM7UUFDekIsY0FBYyxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUM7ZUFDMUUsZUFBZSxDQUFDLGFBQWEsRUFBRSxHQUFHLElBQUksQ0FBQyxjQUFjLEtBQUssQ0FBQyxDQUFDLENBQUM7UUFDbEUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUM7UUFBQyxTQUFTLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQztRQUM3RCxLQUFLLE1BQU0sTUFBTSxJQUFJLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1lBQzNDLGNBQWMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDO21CQUMzRSxDQUFDLGVBQWUsQ0FBQyxhQUFhLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7WUFDbkQsS0FBSyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7WUFBQyxZQUFZLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQzdFLENBQUM7SUFDSCxDQUFDO0lBQ0QsS0FBSyxNQUFNLEtBQUssSUFBSSxNQUFNLEVBQUUsQ0FBQztRQUMzQixNQUFNLE1BQU0sR0FBRyxZQUFZLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUMsRUFBRSxXQUFXLEdBQUcsWUFBWSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsaUJBQWlCLENBQUMsQ0FBQztRQUM3RyxjQUFjLENBQUMsTUFBTSxJQUFJLFdBQVcsSUFBSSxNQUFNLEtBQUssV0FBVyxDQUFDLENBQUM7SUFDbEUsQ0FBQztJQUNELE9BQU8sRUFBRSxXQUFXLEVBQUUsU0FBUyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsRUFBRSxhQUFhLEVBQUUsUUFBUSxFQUFFLGNBQWMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxDQUFDO0FBQzdILENBQUMsQ0FBQyxDQUFDO0FBT0g7Ozs7O0dBS0c7QUFDSCxNQUFNLENBQUMsTUFBTSxzQkFBc0IsR0FBRyxDQUFDLFNBQXlCLEVBQUUsU0FBa0IsRUFBK0IsRUFBRSxDQUNuSCxXQUFXLENBQUMsR0FBRyxFQUFFO0lBQ2YsTUFBTSxJQUFJLEdBQUcseUJBQXlCLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDbEQsSUFBSSxTQUFTLEtBQUssSUFBSTtRQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLENBQUM7SUFDdEQsTUFBTSxJQUFJLEdBQUcseUJBQXlCLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDbEQsZ0dBQWdHO0lBQ2hHLGdGQUFnRjtJQUNoRixJQUFJLElBQUksQ0FBQyxXQUFXLEtBQUssSUFBSSxDQUFDLFdBQVcsSUFBSSxJQUFJLENBQUMsYUFBYSxLQUFLLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztRQUN2RixPQUFPLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsK0JBQStCLEVBQUUsQ0FBQztJQUN4RSxDQUFDO0lBQ0QsSUFBSSxJQUFJLENBQUMsUUFBUSxHQUFHLElBQUksQ0FBQyxRQUFRO1FBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsQ0FBQztJQUNqRSxPQUFPLFlBQVksQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsTUFBTSxFQUFFLDRCQUE0QixFQUFFLENBQUM7QUFDdkgsQ0FBQyxDQUFDLENBQUM7QUFFTCxNQUFNLENBQUMsTUFBTSxxQ0FBcUMsR0FBRyxDQUFDLEtBQWMsRUFBa0MsRUFBRSxDQUFDLFdBQVcsQ0FBQyxHQUFHLEVBQUU7SUFDeEgsTUFBTSxJQUFJLEdBQUcsYUFBYSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7SUFDL0QsT0FBTyxFQUFFLE9BQU8sRUFBRSx5QkFBeUIsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztBQUM5RCxDQUFDLENBQUMsQ0FBQztBQUVILE1BQU0sQ0FBQyxNQUFNLHNDQUFzQyxHQUFHLENBQUMsS0FBYyxFQUFtQyxFQUFFLENBQUMsV0FBVyxDQUFDLEdBQUcsRUFBRTtJQUMxSCxNQUFNLElBQUksR0FBRyxhQUFhLENBQUMsY0FBYyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsaUJBQWlCLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQztJQUNoRixjQUFjLENBQUMsSUFBSSxDQUFDLEtBQUssS0FBSyxNQUFNLElBQUksSUFBSSxDQUFDLEtBQUssS0FBSyxVQUFVLElBQUksSUFBSSxDQUFDLEtBQUssS0FBSyxRQUFRLENBQUMsQ0FBQztJQUM5RixPQUFPLEVBQUUsZUFBZSxFQUFFLElBQUksQ0FBQyxlQUFlLEtBQUssSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsZUFBZSxDQUFDLEVBQUUsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztBQUM3SCxDQUFDLENBQUMsQ0FBQztBQUVIOzs7R0FHRztBQUNILE1BQU0sQ0FBQyxNQUFNLGtDQUFrQyxHQUFHLENBQUMsS0FBYyxFQUFFLGdCQUF5QixFQUFtQyxFQUFFLENBQy9ILFdBQVcsQ0FBQyxHQUFHLEVBQUU7SUFDZixNQUFNLFFBQVEsR0FBRyxzQ0FBc0MsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUMvRCxNQUFNLElBQUksR0FBRyxxQ0FBcUMsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0lBQ3JFLGNBQWMsQ0FBQyxRQUFRLENBQUMsZUFBZSxLQUFLLElBQUksSUFBSSxRQUFRLENBQUMsZUFBZSxJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDdkcsY0FBYyxDQUFDLFFBQVEsQ0FBQyxLQUFLLEtBQUssTUFBTSxJQUFJLFFBQVEsQ0FBQyxlQUFlLEtBQUssSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNoRyxPQUFPLFFBQVEsQ0FBQztBQUNsQixDQUFDLENBQUMsQ0FBQztBQUVMLE1BQU0sQ0FBQyxNQUFNLG1DQUFtQyxHQUFHLENBQUMsS0FBYyxFQUFnQyxFQUFFLENBQUMsV0FBVyxDQUFDLEdBQUcsRUFBRTtJQUNwSCxhQUFhLENBQUMsY0FBYyxDQUFDLEtBQUssQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQ3pDLE9BQU8sRUFBRSxDQUFDO0FBQ1osQ0FBQyxDQUFDLENBQUM7QUFFSCxNQUFNLENBQUMsTUFBTSxvQ0FBb0MsR0FBRyxDQUFDLEtBQWMsRUFBaUMsRUFBRSxDQUFDLFdBQVcsQ0FBQyxHQUFHLEVBQUU7SUFDdEgsTUFBTSxJQUFJLEdBQUcsYUFBYSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7SUFDL0QsT0FBTyxFQUFFLE9BQU8sRUFBRSxJQUFJLENBQUMsT0FBTyxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyx5QkFBeUIsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztBQUM3RixDQUFDLENBQUMsQ0FBQyJ9
|
|
@@ -31,14 +31,13 @@ export interface IRuntimeNetworkAddressPlan {
|
|
|
31
31
|
* One cluster's managed VPN hub, as the cluster's relay bound it.
|
|
32
32
|
*
|
|
33
33
|
* The hub is its cluster's own packet relay, so `clusterId` names both the hub and the authority
|
|
34
|
-
* that may select it, and `address` is an underlay address inside the plan's `vpn.hubPrefix`.
|
|
35
|
-
*
|
|
34
|
+
* that may select it, and `address` is an underlay address inside the plan's `vpn.hubPrefix`.
|
|
35
|
+
* `quicPort` is the port it bound for the one transport a managed hub serves.
|
|
36
36
|
*/
|
|
37
37
|
export interface IRuntimeNetworkVpnHub {
|
|
38
38
|
clusterId: string;
|
|
39
39
|
address: string;
|
|
40
|
-
quicPort: number
|
|
41
|
-
websocketPort: number | null;
|
|
40
|
+
quicPort: number;
|
|
42
41
|
}
|
|
43
42
|
/** What an administrator writes; Cloudly supplies the id and the revision. */
|
|
44
43
|
export type TRuntimeNetworkAddressPlanWritableData = Omit<IRuntimeNetworkAddressPlan, 'id' | 'revision'>;
|
|
@@ -73,28 +72,35 @@ export declare const runtimeNetworkAddressPlanContract: Readonly<{
|
|
|
73
72
|
*/
|
|
74
73
|
readonly maximumControlPrefixLength: 30;
|
|
75
74
|
/**
|
|
76
|
-
* Every transport a hub
|
|
75
|
+
* Every transport a managed hub serves, in the order a node prefers them.
|
|
77
76
|
*
|
|
78
77
|
* One row states all three facts that belong together: the transport a credential names, the
|
|
79
78
|
* suffix its platform endpoint id carries, and the protocol the endpoint is composed with. A hub
|
|
80
|
-
*
|
|
79
|
+
* contributes one endpoint of its address per row, so a transport added here is added once for
|
|
80
|
+
* the id derivation, the composition and the credential binder alike.
|
|
81
|
+
*
|
|
82
|
+
* QUIC is the one row: a managed hub terminates TLS on its QUIC listener alone, so no other
|
|
83
|
+
* transport of it can be dialled over a protected connection.
|
|
81
84
|
*/
|
|
82
|
-
readonly hubTransports: readonly
|
|
85
|
+
readonly hubTransports: readonly Readonly<{
|
|
83
86
|
transport: "quic";
|
|
84
87
|
endpointSuffix: "quic";
|
|
85
88
|
protocol: "udp";
|
|
86
|
-
}>
|
|
87
|
-
transport: "websocket";
|
|
88
|
-
endpointSuffix: "ws";
|
|
89
|
-
protocol: "tcp";
|
|
90
|
-
}>)[];
|
|
89
|
+
}>[];
|
|
91
90
|
}>;
|
|
91
|
+
/**
|
|
92
|
+
* The one reader for a managed VPN control prefix: a canonical private prefix no longer than
|
|
93
|
+
* `maximumControlPrefixLength`. The plan states it for the whole runtime network and a cluster's
|
|
94
|
+
* network repeats it for the hub that binds it, so both read it here and neither can admit a
|
|
95
|
+
* subnet the other would refuse.
|
|
96
|
+
*/
|
|
97
|
+
export declare const runtimeNetworkVpnControlPrefix: (value: unknown) => string;
|
|
92
98
|
/** How a node reaches its cluster's hub, read off the one table that states the transports. */
|
|
93
99
|
export type TRuntimeNetworkVpnTransport = typeof runtimeNetworkAddressPlanContract.hubTransports[number]['transport'];
|
|
94
100
|
/**
|
|
95
|
-
* The platform endpoints one hub contributes to a protected authority: one per transport
|
|
96
|
-
* in id order. Every reader derives the same ids from this one function — the composition
|
|
97
|
-
* the authority, and the router selection that names which of them a node may dial.
|
|
101
|
+
* The platform endpoints one hub contributes to a protected authority: one per transport a managed
|
|
102
|
+
* hub serves, in id order. Every reader derives the same ids from this one function — the composition
|
|
103
|
+
* that seals the authority, and the router selection that names which of them a node may dial.
|
|
98
104
|
*/
|
|
99
105
|
export declare const runtimeNetworkVpnHubEndpoints: (hubValue: unknown) => IRuntimeNetworkProtectedAuthority["platformEndpoints"];
|
|
100
106
|
/**
|
|
@@ -8,38 +8,47 @@ export const runtimeNetworkAddressPlanContract = Object.freeze({
|
|
|
8
8
|
*/
|
|
9
9
|
maximumControlPrefixLength: 30,
|
|
10
10
|
/**
|
|
11
|
-
* Every transport a hub
|
|
11
|
+
* Every transport a managed hub serves, in the order a node prefers them.
|
|
12
12
|
*
|
|
13
13
|
* One row states all three facts that belong together: the transport a credential names, the
|
|
14
14
|
* suffix its platform endpoint id carries, and the protocol the endpoint is composed with. A hub
|
|
15
|
-
*
|
|
15
|
+
* contributes one endpoint of its address per row, so a transport added here is added once for
|
|
16
|
+
* the id derivation, the composition and the credential binder alike.
|
|
17
|
+
*
|
|
18
|
+
* QUIC is the one row: a managed hub terminates TLS on its QUIC listener alone, so no other
|
|
19
|
+
* transport of it can be dialled over a protected connection.
|
|
16
20
|
*/
|
|
17
21
|
hubTransports: Object.freeze([
|
|
18
22
|
Object.freeze({ transport: 'quic', endpointSuffix: 'quic', protocol: 'udp' }),
|
|
19
|
-
Object.freeze({ transport: 'websocket', endpointSuffix: 'ws', protocol: 'tcp' }),
|
|
20
23
|
]),
|
|
21
24
|
});
|
|
22
|
-
/**
|
|
23
|
-
|
|
25
|
+
/**
|
|
26
|
+
* The one reader for a managed VPN control prefix: a canonical private prefix no longer than
|
|
27
|
+
* `maximumControlPrefixLength`. The plan states it for the whole runtime network and a cluster's
|
|
28
|
+
* network repeats it for the hub that binds it, so both read it here and neither can admit a
|
|
29
|
+
* subnet the other would refuse.
|
|
30
|
+
*/
|
|
31
|
+
export const runtimeNetworkVpnControlPrefix = (value) => networkSafe(() => {
|
|
32
|
+
const prefix = networkPrivatePrefix(value);
|
|
33
|
+
requireNetwork(Number(prefix.split('/')[1]) <= runtimeNetworkAddressPlanContract.maximumControlPrefixLength);
|
|
34
|
+
return prefix;
|
|
35
|
+
});
|
|
36
|
+
/** The port a hub bound for one transport; one entry per row of the transport table. */
|
|
37
|
+
const hubPort = (hub, transport) => ({ quic: hub.quicPort })[transport];
|
|
24
38
|
const byEndpointId = (left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
25
39
|
const hubOf = (value) => {
|
|
26
|
-
const item = networkRecord(value, ['clusterId', 'address', 'quicPort'
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
quicPort: port(item.quicPort), websocketPort: port(item.websocketPort) };
|
|
30
|
-
// A hub that bound no transport is not reachable, so it is not a hub a node could be sent to.
|
|
31
|
-
requireNetwork(runtimeNetworkAddressPlanContract.hubTransports.some(row => hubPort(hub, row.transport) !== null));
|
|
32
|
-
return hub;
|
|
40
|
+
const item = networkRecord(value, ['clusterId', 'address', 'quicPort']);
|
|
41
|
+
return { clusterId: networkId(item.clusterId), address: networkIpv4(item.address),
|
|
42
|
+
quicPort: networkCounter(item.quicPort, 65535) };
|
|
33
43
|
};
|
|
34
44
|
/**
|
|
35
|
-
* The platform endpoints one hub contributes to a protected authority: one per transport
|
|
36
|
-
* in id order. Every reader derives the same ids from this one function — the composition
|
|
37
|
-
* the authority, and the router selection that names which of them a node may dial.
|
|
45
|
+
* The platform endpoints one hub contributes to a protected authority: one per transport a managed
|
|
46
|
+
* hub serves, in id order. Every reader derives the same ids from this one function — the composition
|
|
47
|
+
* that seals the authority, and the router selection that names which of them a node may dial.
|
|
38
48
|
*/
|
|
39
49
|
export const runtimeNetworkVpnHubEndpoints = (hubValue) => networkSafe(() => {
|
|
40
50
|
const hub = hubOf(captureNetwork(hubValue));
|
|
41
51
|
return runtimeNetworkAddressPlanContract.hubTransports
|
|
42
|
-
.filter(row => hubPort(hub, row.transport) !== null)
|
|
43
52
|
.map(row => ({ id: `${hub.clusterId}:${row.endpointSuffix}`, address: hub.address,
|
|
44
53
|
protocol: row.protocol, port: hubPort(hub, row.transport) }))
|
|
45
54
|
.sort(byEndpointId);
|
|
@@ -64,9 +73,7 @@ const setsWithHubs = (plan, hubs) => ({
|
|
|
64
73
|
});
|
|
65
74
|
const vpnOf = (value) => {
|
|
66
75
|
const item = networkRecord(value, ['controlPrefix', 'hubPrefix']);
|
|
67
|
-
|
|
68
|
-
requireNetwork(Number(controlPrefix.split('/')[1]) <= runtimeNetworkAddressPlanContract.maximumControlPrefixLength);
|
|
69
|
-
return { controlPrefix, hubPrefix: networkPrefix(item.hubPrefix) };
|
|
76
|
+
return { controlPrefix: runtimeNetworkVpnControlPrefix(item.controlPrefix), hubPrefix: networkPrefix(item.hubPrefix) };
|
|
70
77
|
};
|
|
71
78
|
/**
|
|
72
79
|
* Both VPN prefixes are protected, they hold none of the plan's own pools, resolvers or platform
|
|
@@ -155,4 +162,4 @@ export const admitRuntimeNetworkAddressPlanChange = (previousValue, nextValue) =
|
|
|
155
162
|
return { status: 'accepted', removed: { resolverIds: idsOf(removedOrChanged(previous.resolvers, next.resolvers)),
|
|
156
163
|
platformEndpointIds: idsOf(removedOrChanged(previous.platformEndpoints, next.platformEndpoints)) } };
|
|
157
164
|
});
|
|
158
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmV0d29ya2FkZHJlc3NwbGFuLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvZGF0YS9uZXR3b3JrYWRkcmVzc3BsYW4udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxFQUFFLDZDQUE2QyxFQUFFLCtCQUErQixFQUNyRix3Q0FBd0MsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQzFFLE9BQU8sRUFBRSxjQUFjLEVBQUUsV0FBVyxFQUFFLGVBQWUsRUFBRSxjQUFjLEVBQUUsWUFBWSxFQUFFLFNBQVMsRUFBRSxXQUFXLEVBQUUsV0FBVyxFQUN0SCxlQUFlLEVBQUUsYUFBYSxFQUFFLG1CQUFtQixFQUFFLG9CQUFvQixFQUFFLGFBQWEsRUFBRSxXQUFXLEVBQ3JHLGNBQWMsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBb0V4RCxNQUFNLENBQUMsTUFBTSxpQ0FBaUMsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQzdELEVBQUUsRUFBRSxjQUFjO0lBQ2xCOzs7T0FHRztJQUNILDBCQUEwQixFQUFFLEVBQUU7SUFDOUI7Ozs7OztPQU1HO0lBQ0gsYUFBYSxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUM7UUFDM0IsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsY0FBYyxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLENBQUM7UUFDN0UsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLFNBQVMsRUFBRSxXQUFXLEVBQUUsY0FBYyxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLENBQUM7S0FDakYsQ0FBQztDQUNNLENBQUMsQ0FBQztBQUtaLDJGQUEyRjtBQUMzRixNQUFNLE9BQU8sR0FBRyxDQUFDLEdBQTBCLEVBQUUsU0FBc0MsRUFBaUIsRUFBRSxDQUNwRyxTQUFTLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FB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
|
|
165
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmV0d29ya2FkZHJlc3NwbGFuLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvZGF0YS9uZXR3b3JrYWRkcmVzc3BsYW4udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxFQUFFLDZDQUE2QyxFQUFFLCtCQUErQixFQUNyRix3Q0FBd0MsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQzFFLE9BQU8sRUFBRSxjQUFjLEVBQUUsV0FBVyxFQUFFLGVBQWUsRUFBRSxjQUFjLEVBQUUsWUFBWSxFQUFFLFNBQVMsRUFBRSxXQUFXLEVBQUUsV0FBVyxFQUN0SCxlQUFlLEVBQUUsYUFBYSxFQUFFLG1CQUFtQixFQUFFLG9CQUFvQixFQUFFLGFBQWEsRUFBRSxXQUFXLEVBQ3JHLGNBQWMsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBbUV4RCxNQUFNLENBQUMsTUFBTSxpQ0FBaUMsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQzdELEVBQUUsRUFBRSxjQUFjO0lBQ2xCOzs7T0FHRztJQUNILDBCQUEwQixFQUFFLEVBQUU7SUFDOUI7Ozs7Ozs7Ozs7T0FVRztJQUNILGFBQWEsRUFBRSxNQUFNLENBQUMsTUFBTSxDQUFDO1FBQzNCLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxTQUFTLEVBQUUsTUFBTSxFQUFFLGNBQWMsRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxDQUFDO0tBQzlFLENBQUM7Q0FDTSxDQUFDLENBQUM7QUFFWjs7Ozs7R0FLRztBQUNILE1BQU0sQ0FBQyxNQUFNLDhCQUE4QixHQUFHLENBQUMsS0FBYyxFQUFVLEVBQUUsQ0FBQyxXQUFXLENBQUMsR0FBRyxFQUFFO0lBQ3pGLE1BQU0sTUFBTSxHQUFHLG9CQUFvQixDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzNDLGNBQWMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLGlD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
|
|
@@ -16,13 +16,6 @@ export interface IGetRuntimeManagedVpnCredentialResponse extends IGetRuntimeMana
|
|
|
16
16
|
address: string;
|
|
17
17
|
port: number;
|
|
18
18
|
};
|
|
19
|
-
/**
|
|
20
|
-
* The TLS name the hub's WebSocket transport is dialled under, present exactly when `transport`
|
|
21
|
-
* is `websocket` and absent otherwise: QUIC is dialled on the address alone. It is the hub
|
|
22
|
-
* relay's own origin hostname, which the node cross-checks against the relay origin it stores,
|
|
23
|
-
* so a name the node does not already trust ends the handshake before any native effect.
|
|
24
|
-
*/
|
|
25
|
-
serverName?: string;
|
|
26
19
|
authorityId: string;
|
|
27
20
|
serverPublicKey: string;
|
|
28
21
|
clientPublicKey: string;
|
|
@@ -7,20 +7,6 @@ export const snapshotGetRuntimeManagedVpnCredentialRequest = (value) => networkS
|
|
|
7
7
|
const item = networkRecord(captureNetwork(value), ['session', 'projection']);
|
|
8
8
|
return { session: snapshotRuntimeSessionBinding(item.session), projection: networkReference(item.projection) };
|
|
9
9
|
});
|
|
10
|
-
/**
|
|
11
|
-
* The TLS name a WebSocket hub is dialled under: a lower-case domain name of at least two labels,
|
|
12
|
-
* which is what a relay origin's hostname is and what a certificate can be issued for. An address
|
|
13
|
-
* literal is not one, so a node can never be sent to a name nothing vouches for.
|
|
14
|
-
*/
|
|
15
|
-
const serverName = (value) => {
|
|
16
|
-
requireNetwork(typeof value === 'string' && value.length <= 253);
|
|
17
|
-
const labels = value.split('.');
|
|
18
|
-
// The last label starts with a letter, which is what makes this a name and not an address
|
|
19
|
-
// literal: a hub reached by address is reached over QUIC, which needs no name at all.
|
|
20
|
-
requireNetwork(labels.length >= 2 && labels.every(label => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label))
|
|
21
|
-
&& /^[a-z]/.test(labels[labels.length - 1]));
|
|
22
|
-
return value;
|
|
23
|
-
};
|
|
24
10
|
const transportOf = (value) => {
|
|
25
11
|
const row = runtimeNetworkAddressPlanContract.hubTransports.find(candidate => candidate.transport === value);
|
|
26
12
|
requireNetwork(row);
|
|
@@ -28,14 +14,11 @@ const transportOf = (value) => {
|
|
|
28
14
|
};
|
|
29
15
|
export const snapshotGetRuntimeManagedVpnCredentialResponse = (value) => networkSafe(() => {
|
|
30
16
|
const item = networkRecord(captureNetwork(value), ['session', 'projection', 'transport', 'hub', 'authorityId',
|
|
31
|
-
'serverPublicKey', 'clientPublicKey', 'clientPrivateKey', 'expiresAt']
|
|
32
|
-
const hub = networkRecord(item.hub, ['id', 'address', 'port'])
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
requireNetwork(Object.hasOwn(item, 'serverName') === (transport === 'websocket'));
|
|
36
|
-
return { session: snapshotRuntimeSessionBinding(item.session), projection: networkReference(item.projection), transport,
|
|
17
|
+
'serverPublicKey', 'clientPublicKey', 'clientPrivateKey', 'expiresAt']);
|
|
18
|
+
const hub = networkRecord(item.hub, ['id', 'address', 'port']);
|
|
19
|
+
return { session: snapshotRuntimeSessionBinding(item.session), projection: networkReference(item.projection),
|
|
20
|
+
transport: transportOf(item.transport),
|
|
37
21
|
hub: { id: networkId(hub.id), address: networkIpv4(hub.address), port: networkCounter(hub.port, 65535) },
|
|
38
|
-
...(transport === 'websocket' ? { serverName: serverName(item.serverName) } : {}),
|
|
39
22
|
authorityId: networkId(item.authorityId), serverPublicKey: networkNoiseKey(item.serverPublicKey),
|
|
40
23
|
clientPublicKey: networkNoiseKey(item.clientPublicKey), clientPrivateKey: networkNoiseKey(item.clientPrivateKey),
|
|
41
24
|
expiresAt: networkCounter(item.expiresAt) };
|
|
@@ -87,4 +70,4 @@ export const bindGetRuntimeManagedVpnCredentialResponse = async (value, requestV
|
|
|
87
70
|
return failNetwork();
|
|
88
71
|
}
|
|
89
72
|
};
|
|
90
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
73
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmV0d29ya2NyZWRlbnRpYWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL25ldHdvcmtjcmVkZW50aWFsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxpQ0FBaUMsRUFBb0MsTUFBTSx5QkFBeUIsQ0FBQztBQUM5RyxPQUFPLEVBQUUsNkJBQTZCLEVBQStCLE1BQU0scUJBQXFCLENBQUM7QUFDakcsT0FBTyxFQUFFLGdDQUFnQyxFQUFrQyxNQUFNLHdCQUF3QixDQUFDO0FBQzFHLE9BQU8sRUFBRSxxQ0FBcUMsRUFBRSxxQ0FBcUMsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQ2pILE9BQU8sRUFBRSxjQUFjLEVBQUUsV0FBVyxFQUFFLGNBQWMsRUFBRSx5QkFBeUIsRUFBRSxZQUFZLEVBQUUsU0FBUyxFQUFFLFdBQVcsRUFDbkgsZUFBZSxFQUFFLGFBQWEsRUFBRSxnQkFBZ0IsRUFBRSxrQkFBa0IsRUFBRSxXQUFXLEVBQUUsZ0JBQWdCLEVBQ25HLGNBQWMsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBeUJ4RCxNQUFNLENBQUMsTUFBTSw2Q0FBNkMsR0FBRyxDQUFDLEtBQWMsRUFBMEMsRUFBRSxDQUFDLFdBQVcsQ0FBQyxHQUFHLEVBQUU7SUFDeEksTUFBTSxJQUFJLEdBQUcsYUFBYSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFNBQVMsRUFBRSxZQUFZLENBQUMsQ0FBQyxDQUFDO0lBQzdFLE9BQU8sRUFBRSxPQUFPLEVBQUUsNkJBQTZCLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxFQUFFLFVBQVUsRUFBRSxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQztBQUNqSCxDQUFDLENBQUMsQ0FBQztBQUVILE1BQU0sV0FBVyxHQUFHLENBQUMsS0FBYyxFQUErQixFQUFFO0lBQ2xFLE1BQU0sR0FBRyxHQUFHLGlDQUFpQyxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxTQUFTLENBQUMsU0FBUyxLQUFLLEtBQUssQ0FBQyxDQUFDO0lBQzdHLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUFDLE9BQU8sR0FBRyxDQUFDLFNBQVMsQ0FBQztBQUM1QyxDQUFDLENBQUM7QUFFRixNQUFNLENBQUMsTUFBTSw4Q0FBOEMsR0FBRyxDQUFDLEtBQWMsRUFBMkMsRUFBRSxDQUFDLFdBQVcsQ0FBQyxHQUFHLEVBQUU7SUFDMUksTUFBTSxJQUFJLEdBQUcsYUFBYSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFNBQVMsRUFBRSxZQUFZLEVBQUUsV0FBVyxFQUFFLEtBQUssRUFBRSxhQUFhO1FBQzNHLGlCQUFpQixFQUFFLGlCQUFpQixFQUFFLGtCQUFrQixFQUFFLFdBQVcsQ0FBQyxDQUFDLENBQUM7SUFDMUUsTUFBTSxHQUFHLEdBQUcsYUFBYSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFJLEVBQUUsU0FBUyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7SUFDL0QsT0FBTyxFQUFFLE9BQU8sRUFBRSw2QkFBNkIsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUUsVUFBVSxFQUFFLGdCQUFnQixDQUFDLElBQUksQ0FBQyxVQUFVLENBQUM7UUFDMUcsU0FBUyxFQUFFLFdBQVcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDO1FBQ3RDLEdBQUcsRUFBRSxFQUFFLEVBQUUsRUFBRSxTQUFTLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxFQUFFLE9BQU8sRUFBRSxXQUFXLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxFQUFFLElBQUksRUFBRSxjQUFjLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsRUFBRTtRQUN4RyxXQUFXLEVBQUUsU0FBUyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsRUFBRSxlQUFlLEVBQUUsZUFBZSxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUM7UUFDaEcsZUFBZSxFQUFFLGVBQWUsQ0FBQyxJQUFJLENBQUMsZUFBZSxDQUFDLEVBQUUsZ0JBQWdCLEVBQUUsZUFBZSxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQztRQUNoSCxTQUFTLEVBQUUsY0FBYyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDO0FBQ2hELENBQUMsQ0FBQyxDQUFDO0FBRUgsTUFBTSxXQUFXLEdBQUcsQ0FBQyxPQUErQyxFQUNsRSxVQUFxQyxFQUFFLE9BQStCLEVBQVEsRUFBRTtJQUNoRixjQUFjLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLElBQUksWUFBWSxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsa0JBQWtCLENBQUMsVUFBVSxDQUFDLENBQUM7V0FDcEgsZ0JBQWdCLENBQUMsVUFBVSxFQUFFLE9BQU8sQ0FBQyxJQUFJLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUNwRSxDQUFDLENBQUM7QUFFRjtvRkFDb0Y7QUFDcEYsTUFBTSxDQUFDLE1BQU0seUNBQXlDLEdBQUcsS0FBSyxFQUFFLEtBQWMsRUFBRSxlQUF3QixFQUN0RyxtQkFBNEIsRUFBbUQsRUFBRTtJQUNqRixJQUFJLENBQUM7UUFDSCxNQUFNLE9BQU8sR0FBRyw2Q0FBNkMsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUNyRSxNQUFNLE9BQU8sR0FBRyw2QkFBNkIsQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO1FBQ25FLE1BQU0sVUFBVSxHQUFHLE1BQU0sZ0NBQWdDLENBQUMsZUFBZSxDQUFDLENBQUM7UUFDM0UsV0FBVyxDQUFDLE9BQU8sRUFBRSxVQUFVLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDMUMsT0FBTyxPQUFPLENBQUM7SUFDakIsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUFDLE9BQU8sV0FBVyxFQUFFLENBQUM7SUFBQyxDQUFDO0FBQ25DLENBQUMsQ0FBQztBQUVGOzs7Ozt5Q0FLeUM7QUFDekMsTUFBTSxDQUFDLE1BQU0sMENBQTBDLEdBQUcsS0FBSyxFQUFFLEtBQWMsRUFBRSxZQUFxQixFQUNwRyxlQUF3QixFQUFFLG1CQUE0QixFQUFFLFFBQWlCLEVBQ3pFLGVBQStCLElBQUksRUFBb0QsRUFBRTtJQUN6RixJQUFJLENBQUM7UUFDSCxNQUFNLFFBQVEsR0FBRyw4Q0FBOEMsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN2RSxNQUFNLE9BQU8sR0FBRyw2Q0FBNkMsQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUM1RSxNQUFNLE9BQU8sR0FBRyw2QkFBNkIsQ0FBQyxtQkFBbUIsQ0FBQyxFQUFFLEdBQUcsR0FBRyxjQUFjLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDbkcsTUFBTSxZQUFZLEdBQUcsWUFBWSxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxxQ0FBcUMsQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUN4RyxNQUFNLFVBQVUsR0FBRyxNQUFNLGdDQUFnQyxDQUFDLGVBQWUsQ0FBQyxDQUFDO1FBQzNFLE1BQU0sT0FBTyxHQUFHLFlBQVksS0FBSyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsTUFBTSxxQ0FBcUMsQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUN6RyxNQUFNLE1BQU0sR0FBRyx5QkFBeUIsQ0FBQyxVQUFVLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDOUQsV0FBVyxDQUFDLE9BQU8sRUFBRSxVQUFVLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDMUMsY0FBYyxDQUFDLFlBQVksQ0FBQyxRQUFRLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxZQUFZLENBQUMsUUFBUSxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQztRQUN6SCxjQUFjLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxtQkFBbUIsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO1FBQ2pGLE1BQU0sUUFBUSxHQUFHLFVBQVUsQ0FBQyxrQkFBa0IsQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsRUFBRSxLQUFLLFFBQVEsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDM0csTUFBTSxRQUFRLEdBQUcsaUNBQWlDLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxTQUFTLEtBQUssUUFBUSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQ25ILGNBQWMsQ0FBQyxRQUFRLElBQUksUUFBUSxJQUFJLFFBQVEsQ0FBQyxRQUFRLEtBQUssUUFBUSxDQUFDLFFBQVE7ZUFDekUsUUFBUSxDQUFDLE9BQU8sS0FBSyxRQUFRLENBQUMsR0FBRyxDQUFDLE9BQU8sSUFBSSxRQUFRLENBQUMsSUFBSSxLQUFLLFFBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDdkYsY0FBYyxDQUFDLE1BQU0sQ0FBQyxTQUFTLElBQUksR0FBRyxJQUFJLEdBQUcsR0FBRyxRQUFRLENBQUMsU0FBUyxJQUFJLFFBQVEsQ0FBQyxTQUFTLElBQUksTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzlHLE9BQU8sUUFBUSxDQUFDO0lBQ2xCLENBQUM7SUFBQyxNQUFNLENBQUM7UUFBQyxPQUFPLFdBQVcsRUFBRSxDQUFDO0lBQUMsQ0FBQztBQUNuQyxDQUFDLENBQUMifQ==
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -519,11 +519,10 @@ uses `IGetRuntimeManagedVpnCredentialRequest` and
|
|
|
519
519
|
`IGetRuntimeManagedVpnCredentialResponse`. Its request carries the exact current
|
|
520
520
|
session and signed projection reference. The secret response repeats those
|
|
521
521
|
bindings and supplies the `hub` tuple the node dials, the `transport` it dials it
|
|
522
|
-
over (`quic
|
|
523
|
-
key, the client keypair and the expiry in Unix
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
`quic`, which is dialled on the address alone. The hub is the node's own cluster's
|
|
522
|
+
over (`quic`, the one transport a managed hub serves), the managed authority ID,
|
|
523
|
+
the Noise server public key, the client keypair and the expiry in Unix
|
|
524
|
+
milliseconds. The response states no TLS name: QUIC is dialled on the address
|
|
525
|
+
alone. The hub is the node's own cluster's
|
|
527
526
|
relay (see [Cluster VPN Hub And Network](#cluster-vpn-hub-and-network)). Keep the
|
|
528
527
|
response process-local; never persist, hash, log or embed it in a signed projection.
|
|
529
528
|
|
|
@@ -534,7 +533,7 @@ validates the complete projection digest and binds node, controller, namespace,
|
|
|
534
533
|
physical-session identity and projection reference. The response binder takes
|
|
535
534
|
`(response, request, projection, trustedSession, now, renewal = null)` and additionally requires the
|
|
536
535
|
hub's exact tuple to be selected in `router.platformEndpointIds`, carrying the protocol of the
|
|
537
|
-
credential's transport — `udp` for `quic
|
|
536
|
+
credential's transport — `udp` for `quic`, as
|
|
538
537
|
`runtimeNetworkAddressPlanContract.hubTransports` states. A relay therefore cannot name itself: the
|
|
539
538
|
endpoint has to be one the node's **signed** projection already selected.
|
|
540
539
|
Expiry must be after the caller-qualified time and no later than the referenced
|
|
@@ -702,12 +701,15 @@ other — control addresses live inside the tunnel, hub endpoints on the underla
|
|
|
702
701
|
additionally a private prefix no longer than /30
|
|
703
702
|
(`runtimeNetworkAddressPlanContract.maximumControlPrefixLength`); no minimum length is stated,
|
|
704
703
|
because the private-range rule already keeps it at /8 or longer.
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
`
|
|
709
|
-
|
|
710
|
-
`
|
|
704
|
+
`data.runtimeNetworkVpnControlPrefix(value)` is that reader on its own, exported because a
|
|
705
|
+
cluster's VPN network repeats the prefix its hub binds and has to read it exactly as the plan does.
|
|
706
|
+
|
|
707
|
+
`data.IRuntimeNetworkVpnHub { clusterId, address, quicPort }` is one cluster's hub as its relay
|
|
708
|
+
bound it. `runtimeNetworkVpnHubEndpoints(hub)` is the one derivation of what it contributes:
|
|
709
|
+
`<clusterId>:quic` on `udp`, one endpoint per row of
|
|
710
|
+
`runtimeNetworkAddressPlanContract.hubTransports`. That table has one row, because a managed hub
|
|
711
|
+
terminates TLS on its QUIC listener alone and no other transport of it can be dialled over a
|
|
712
|
+
protected connection. Every reader calls the derivation — the composition
|
|
711
713
|
that seals the authority and the router selection that names which endpoint a node may dial — so
|
|
712
714
|
two readers can never derive two ids for one hub.
|
|
713
715
|
`composeRuntimeNetworkProtectedAuthority(plan, frame, hubs)` is the composition itself: the producer
|
|
@@ -716,7 +718,7 @@ previous reference and egress authorities) and the hubs of the clusters that reg
|
|
|
716
718
|
receives the complete authority with its digest, so the validator and the protection producer can
|
|
717
719
|
never compose differently. Each hub's address must sit inside `vpn.hubPrefix` and one cluster is
|
|
718
720
|
named once; an empty `hubs` composes the authority of a runtime network whose clusters have no hub
|
|
719
|
-
yet. One hub costs
|
|
721
|
+
yet. One hub costs one of the authority's 64 platform endpoints, so 64 clusters fit one
|
|
720
722
|
authority.
|
|
721
723
|
|
|
722
724
|
`admitRuntimeNetworkAddressPlanChange(previous | null, next)` is the pure decision for a plan write at
|
|
@@ -1737,27 +1739,33 @@ Cloudly keeps every authority — it compiles the network and issues each node's
|
|
|
1737
1739
|
carries no packet.
|
|
1738
1740
|
|
|
1739
1741
|
A relay that bound a hub says so in its registration, in `relay.vpnHub`:
|
|
1740
|
-
`data.IClusterRelayVpnHub { publicKey, address, quicPort
|
|
1742
|
+
`data.IClusterRelayVpnHub { publicKey, address, quicPort }`. It reports what it
|
|
1741
1743
|
bound, exactly like `listenAddress` and `listenPort`: `address` is an IPv4 literal inside the
|
|
1742
|
-
address plan's `vpn.hubPrefix
|
|
1743
|
-
|
|
1744
|
+
address plan's `vpn.hubPrefix` and `quicPort` is the port it bound for the one transport a managed
|
|
1745
|
+
hub serves. `publicKey` is the hub's Noise public key in the one canonical 32-byte base64 encoding
|
|
1744
1746
|
every Noise key of this contract is read with — this advertisement, the managed VPN credential's
|
|
1745
|
-
keys and a cluster network's nodes all pass the same reader. A relay names no endpoint id
|
|
1746
|
-
|
|
1747
|
-
[Runtime Network Address Plan](#runtime-network-address-plan))
|
|
1748
|
-
origin it published for that relay. The member is optional — a relay that serves no hub registers
|
|
1747
|
+
keys and a cluster network's nodes all pass the same reader. A relay names no endpoint id of its
|
|
1748
|
+
own: Cloudly composes the endpoints with `runtimeNetworkVpnHubEndpoints` (see
|
|
1749
|
+
[Runtime Network Address Plan](#runtime-network-address-plan)). The member is optional — a relay that serves no hub registers
|
|
1749
1750
|
exactly as before, and Cloudly selects no hub for that cluster, stating `relay-vpn-hub-absent`.
|
|
1750
1751
|
`validateClusterRelayVpnHub(value, path?)` returns one named reason per violation.
|
|
1751
1752
|
|
|
1752
|
-
`data.IClusterVpnNetwork { authorityId, revision, nodes, grants }` is the whole
|
|
1753
|
-
cluster. It is never a patch: a node or a grant the network does not carry is
|
|
1754
|
-
membership is presence, so there is no enabled flag.
|
|
1753
|
+
`data.IClusterVpnNetwork { authorityId, controlPrefix, revision, nodes, grants }` is the whole
|
|
1754
|
+
managed VPN of one cluster. It is never a patch: a node or a grant the network does not carry is
|
|
1755
|
+
withdrawn, and membership is presence, so there is no enabled flag. `controlPrefix` is the subnet
|
|
1756
|
+
the cluster's hub serves its control plane on — the address plan's `vpn.controlPrefix`, read by the
|
|
1757
|
+
same `runtimeNetworkVpnControlPrefix` reader. A hub fixes that subnet when it binds and no other
|
|
1758
|
+
carrier hands a relay the address plan, so the network it is pushed states it; the plan contract
|
|
1759
|
+
never lets it change, which is why a network that moves the prefix under the same authority is
|
|
1760
|
+
refused as `vpn-network-authority-foreign` rather than by a name of its own. Each node states `{ nodeId, publicKey,
|
|
1755
1761
|
controlAddress, expiresAt, workloadPrefixes }`, where `expiresAt` is that node's credential expiry
|
|
1756
1762
|
in Unix milliseconds and each prefix is `{ cidr, policyDomain }`. Each grant is one directed
|
|
1757
1763
|
`{ sourceDomain, destinationDomain }` pair. `clusterVpnControlPolicyDomain(address)` and
|
|
1758
1764
|
`clusterVpnWorkloadPolicyDomain(leaseDigest)` state the domain names once for both sides.
|
|
1759
1765
|
|
|
1760
|
-
`snapshotClusterVpnNetwork` requires sorted, unique members, prefixes and grants,
|
|
1766
|
+
`snapshotClusterVpnNetwork` requires sorted, unique members, prefixes and grants, requires every
|
|
1767
|
+
control address to lie inside `controlPrefix` and every workload prefix to stay clear of it — the
|
|
1768
|
+
two rules the hub enforces against the subnet it bound — and requires
|
|
1761
1769
|
every grant to join two workload domains the network itself carries, on two different nodes. That
|
|
1762
1770
|
is what a per-cluster hub means: a grant whose other end sits on another cluster's hub cannot be
|
|
1763
1771
|
expressed at all, which is why Cloudly refuses it while it compiles, by name, with
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/data/cluster.ts
CHANGED
|
@@ -70,17 +70,16 @@ export interface IClusterRelay {
|
|
|
70
70
|
* The relay is its cluster's packet relay: every node of the cluster dials this hub and no
|
|
71
71
|
* inter-node packet leaves the cluster. It reports what it bound, exactly like `listenAddress` and
|
|
72
72
|
* `listenPort`, and Cloudly composes the platform endpoints from it — a relay names no endpoint id
|
|
73
|
-
*
|
|
73
|
+
* of its own. `publicKey` is the hub's Noise public key in the one canonical
|
|
74
74
|
* 32-byte base64 encoding every Noise key of this contract is read with; it is public material and
|
|
75
|
-
* authenticates nothing by itself.
|
|
76
|
-
*
|
|
75
|
+
* authenticates nothing by itself. `quicPort` is the port it bound for the one transport a managed
|
|
76
|
+
* hub serves, so a relay that advertises a hub advertises a reachable one.
|
|
77
77
|
*/
|
|
78
78
|
export interface IClusterRelayVpnHub {
|
|
79
79
|
publicKey: string;
|
|
80
80
|
/** The address the hub bound, as an IPv4 literal inside the address plan's `vpn.hubPrefix`. */
|
|
81
81
|
address: string;
|
|
82
|
-
quicPort: number
|
|
83
|
-
websocketPort: number | null;
|
|
82
|
+
quicPort: number;
|
|
84
83
|
}
|
|
85
84
|
|
|
86
85
|
/**
|
|
@@ -274,13 +273,6 @@ const validateClusterRelayVpnKey = (valueArg: unknown, pathArg: string): string[
|
|
|
274
273
|
}
|
|
275
274
|
};
|
|
276
275
|
|
|
277
|
-
/** A port the hub bound, or `null` for a transport it did not bind. */
|
|
278
|
-
const validateClusterRelayHubPort = (valueArg: unknown, pathArg: string): string[] => (
|
|
279
|
-
valueArg === null || validateClusterRelayListenPort(valueArg, pathArg).length === 0
|
|
280
|
-
? []
|
|
281
|
-
: [`${pathArg} must be null or an integer from 1 to 65535`]
|
|
282
|
-
);
|
|
283
|
-
|
|
284
276
|
/**
|
|
285
277
|
* Validates one hub advertisement and names every reason it is refused.
|
|
286
278
|
*
|
|
@@ -292,19 +284,14 @@ export const validateClusterRelayVpnHub = (
|
|
|
292
284
|
pathArg = 'cluster relay vpn hub',
|
|
293
285
|
): string[] => {
|
|
294
286
|
if (!isRecord(valueArg)
|
|
295
|
-
|| !hasExactKeys(valueArg, ['publicKey', 'address', 'quicPort'
|
|
287
|
+
|| !hasExactKeys(valueArg, ['publicKey', 'address', 'quicPort'])) {
|
|
296
288
|
return [`${pathArg} must use its exact schema`];
|
|
297
289
|
}
|
|
298
|
-
|
|
290
|
+
return [
|
|
299
291
|
...validateClusterRelayVpnKey(valueArg.publicKey, `${pathArg} publicKey`),
|
|
300
292
|
...validateClusterRelayListenAddress(valueArg.address, `${pathArg} address`),
|
|
301
|
-
...
|
|
302
|
-
...validateClusterRelayHubPort(valueArg.websocketPort, `${pathArg} websocketPort`),
|
|
293
|
+
...validateClusterRelayListenPort(valueArg.quicPort, `${pathArg} quicPort`),
|
|
303
294
|
];
|
|
304
|
-
if (valueArg.quicPort === null && valueArg.websocketPort === null) {
|
|
305
|
-
errors.push(`${pathArg} must bind at least one of quicPort and websocketPort`);
|
|
306
|
-
}
|
|
307
|
-
return errors;
|
|
308
295
|
};
|
|
309
296
|
|
|
310
297
|
const validateClusterRelayEndpoint = (valueArg: unknown, pathArg: string): string[] => {
|
package/ts/data/clustervpn.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { runtimeNetworkVpnControlPrefix } from './networkaddressplan.js';
|
|
2
|
+
import { captureNetwork, failNetwork, networkCanonical, networkContains, networkCounter, networkEqual, networkId, networkIpv4,
|
|
3
|
+
networkList, networkNodeId, networkNoiseKey, networkOverlaps, networkPrivatePrefix, networkRecord, networkSafe,
|
|
4
|
+
networkSorted, requireNetwork } from '../private/networkcontract.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* One workload prefix a node owns inside its cluster's managed VPN, and the policy domain that
|
|
@@ -40,9 +41,16 @@ export interface IClusterVpnNetworkGrant {
|
|
|
40
41
|
* a grant that is missing is withdrawn. `revision` is Cloudly's own counter for this authority and
|
|
41
42
|
* only ever rises; `authorityId` names the authority the hub is bound to, so a network compiled for
|
|
42
43
|
* another cluster is refused rather than applied.
|
|
44
|
+
*
|
|
45
|
+
* `controlPrefix` is the subnet the hub's control plane serves: the address plan's
|
|
46
|
+
* `vpn.controlPrefix`, which the plan contract never lets change, stated here because a hub fixes
|
|
47
|
+
* its control subnet when it binds and no other carrier hands a relay the plan. Without it a relay
|
|
48
|
+
* could only infer a subnet from the members it was sent, which is how a hub comes to disagree with
|
|
49
|
+
* the plan it serves.
|
|
43
50
|
*/
|
|
44
51
|
export interface IClusterVpnNetwork {
|
|
45
52
|
authorityId: string;
|
|
53
|
+
controlPrefix: string;
|
|
46
54
|
revision: number;
|
|
47
55
|
nodes: IClusterVpnNetworkNode[];
|
|
48
56
|
grants: IClusterVpnNetworkGrant[];
|
|
@@ -168,20 +176,27 @@ const grantKey = (grant: IClusterVpnNetworkGrant): string => `${grant.sourceDoma
|
|
|
168
176
|
* workload domain this network itself carries, on two different nodes. That last rule is what a
|
|
169
177
|
* per-cluster hub means: a grant whose other end sits on another cluster's hub cannot be expressed
|
|
170
178
|
* here at all, which is why Cloudly refuses it by name while it compiles instead of pushing it.
|
|
179
|
+
*
|
|
180
|
+
* Every control address lies inside `controlPrefix` and no workload prefix meets it — the two rules
|
|
181
|
+
* a hub enforces against the subnet it bound. Checking them while the network is compiled turns a
|
|
182
|
+
* push the hub could only refuse into a network Cloudly never states.
|
|
171
183
|
*/
|
|
172
184
|
export const snapshotClusterVpnNetwork = (value: unknown): IClusterVpnNetwork => networkSafe(() => {
|
|
173
185
|
const captured = captureNetwork(value);
|
|
174
186
|
requireNetwork(new TextEncoder().encode(networkCanonical(captured)).length <= clusterVpnNetworkContract.maximumCanonicalBytes);
|
|
175
|
-
const item = networkRecord(captured, ['authorityId', 'revision', 'nodes', 'grants']);
|
|
187
|
+
const item = networkRecord(captured, ['authorityId', 'controlPrefix', 'revision', 'nodes', 'grants']);
|
|
188
|
+
const controlPrefix = runtimeNetworkVpnControlPrefix(item.controlPrefix);
|
|
176
189
|
const nodes = networkSorted(networkList(item.nodes, clusterVpnNetworkContract.maximumNodes, nodeOf), node => node.nodeId);
|
|
177
190
|
const grants = networkSorted(networkList(item.grants, clusterVpnNetworkContract.maximumGrants, grantOf), grantKey);
|
|
178
191
|
const domainOwners = new Map<string, string>();
|
|
179
192
|
const addresses = new Set<string>(), keys = new Set<string>(), cidrs = new Set<string>();
|
|
180
193
|
for (const node of nodes) {
|
|
181
|
-
requireNetwork(!keys.has(node.publicKey) && !addresses.has(node.controlAddress)
|
|
194
|
+
requireNetwork(!keys.has(node.publicKey) && !addresses.has(node.controlAddress)
|
|
195
|
+
&& networkContains(controlPrefix, `${node.controlAddress}/32`));
|
|
182
196
|
keys.add(node.publicKey); addresses.add(node.controlAddress);
|
|
183
197
|
for (const prefix of node.workloadPrefixes) {
|
|
184
|
-
requireNetwork(!cidrs.has(prefix.cidr) && !domainOwners.has(prefix.policyDomain)
|
|
198
|
+
requireNetwork(!cidrs.has(prefix.cidr) && !domainOwners.has(prefix.policyDomain)
|
|
199
|
+
&& !networkOverlaps(controlPrefix, prefix.cidr));
|
|
185
200
|
cidrs.add(prefix.cidr); domainOwners.set(prefix.policyDomain, node.nodeId);
|
|
186
201
|
}
|
|
187
202
|
}
|
|
@@ -189,7 +204,7 @@ export const snapshotClusterVpnNetwork = (value: unknown): IClusterVpnNetwork =>
|
|
|
189
204
|
const source = domainOwners.get(grant.sourceDomain), destination = domainOwners.get(grant.destinationDomain);
|
|
190
205
|
requireNetwork(source && destination && source !== destination);
|
|
191
206
|
}
|
|
192
|
-
return { authorityId: networkId(item.authorityId), revision: networkCounter(item.revision), nodes, grants };
|
|
207
|
+
return { authorityId: networkId(item.authorityId), controlPrefix, revision: networkCounter(item.revision), nodes, grants };
|
|
193
208
|
});
|
|
194
209
|
|
|
195
210
|
export type TClusterVpnNetworkAdmission =
|
|
@@ -208,7 +223,11 @@ export const admitClusterVpnNetwork = (heldValue: unknown | null, nextValue: unk
|
|
|
208
223
|
const next = snapshotClusterVpnNetwork(nextValue);
|
|
209
224
|
if (heldValue === null) return { status: 'accepted' };
|
|
210
225
|
const held = snapshotClusterVpnNetwork(heldValue);
|
|
211
|
-
|
|
226
|
+
// A hub binds one control subnet for its lifetime, so a network that moves the prefix under the
|
|
227
|
+
// same authority is as foreign to this hub as one that names another authority.
|
|
228
|
+
if (held.authorityId !== next.authorityId || held.controlPrefix !== next.controlPrefix) {
|
|
229
|
+
return { status: 'refused', reason: 'vpn-network-authority-foreign' };
|
|
230
|
+
}
|
|
212
231
|
if (next.revision > held.revision) return { status: 'accepted' };
|
|
213
232
|
return networkEqual(held, next) ? { status: 'replay' } : { status: 'refused', reason: 'vpn-network-revision-stale' };
|
|
214
233
|
});
|
|
@@ -39,14 +39,13 @@ export interface IRuntimeNetworkAddressPlan {
|
|
|
39
39
|
* One cluster's managed VPN hub, as the cluster's relay bound it.
|
|
40
40
|
*
|
|
41
41
|
* The hub is its cluster's own packet relay, so `clusterId` names both the hub and the authority
|
|
42
|
-
* that may select it, and `address` is an underlay address inside the plan's `vpn.hubPrefix`.
|
|
43
|
-
*
|
|
42
|
+
* that may select it, and `address` is an underlay address inside the plan's `vpn.hubPrefix`.
|
|
43
|
+
* `quicPort` is the port it bound for the one transport a managed hub serves.
|
|
44
44
|
*/
|
|
45
45
|
export interface IRuntimeNetworkVpnHub {
|
|
46
46
|
clusterId: string;
|
|
47
47
|
address: string;
|
|
48
|
-
quicPort: number
|
|
49
|
-
websocketPort: number | null;
|
|
48
|
+
quicPort: number;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
/** What an administrator writes; Cloudly supplies the id and the revision. */
|
|
@@ -80,50 +79,60 @@ export const runtimeNetworkAddressPlanContract = Object.freeze({
|
|
|
80
79
|
*/
|
|
81
80
|
maximumControlPrefixLength: 30,
|
|
82
81
|
/**
|
|
83
|
-
* Every transport a hub
|
|
82
|
+
* Every transport a managed hub serves, in the order a node prefers them.
|
|
84
83
|
*
|
|
85
84
|
* One row states all three facts that belong together: the transport a credential names, the
|
|
86
85
|
* suffix its platform endpoint id carries, and the protocol the endpoint is composed with. A hub
|
|
87
|
-
*
|
|
86
|
+
* contributes one endpoint of its address per row, so a transport added here is added once for
|
|
87
|
+
* the id derivation, the composition and the credential binder alike.
|
|
88
|
+
*
|
|
89
|
+
* QUIC is the one row: a managed hub terminates TLS on its QUIC listener alone, so no other
|
|
90
|
+
* transport of it can be dialled over a protected connection.
|
|
88
91
|
*/
|
|
89
92
|
hubTransports: Object.freeze([
|
|
90
93
|
Object.freeze({ transport: 'quic', endpointSuffix: 'quic', protocol: 'udp' }),
|
|
91
|
-
Object.freeze({ transport: 'websocket', endpointSuffix: 'ws', protocol: 'tcp' }),
|
|
92
94
|
]),
|
|
93
95
|
} as const);
|
|
94
96
|
|
|
97
|
+
/**
|
|
98
|
+
* The one reader for a managed VPN control prefix: a canonical private prefix no longer than
|
|
99
|
+
* `maximumControlPrefixLength`. The plan states it for the whole runtime network and a cluster's
|
|
100
|
+
* network repeats it for the hub that binds it, so both read it here and neither can admit a
|
|
101
|
+
* subnet the other would refuse.
|
|
102
|
+
*/
|
|
103
|
+
export const runtimeNetworkVpnControlPrefix = (value: unknown): string => networkSafe(() => {
|
|
104
|
+
const prefix = networkPrivatePrefix(value);
|
|
105
|
+
requireNetwork(Number(prefix.split('/')[1]) <= runtimeNetworkAddressPlanContract.maximumControlPrefixLength);
|
|
106
|
+
return prefix;
|
|
107
|
+
});
|
|
108
|
+
|
|
95
109
|
/** How a node reaches its cluster's hub, read off the one table that states the transports. */
|
|
96
110
|
export type TRuntimeNetworkVpnTransport = typeof runtimeNetworkAddressPlanContract.hubTransports[number]['transport'];
|
|
97
111
|
|
|
98
|
-
/** The port a hub bound for one transport
|
|
99
|
-
const hubPort = (hub: IRuntimeNetworkVpnHub, transport: TRuntimeNetworkVpnTransport): number
|
|
100
|
-
|
|
112
|
+
/** The port a hub bound for one transport; one entry per row of the transport table. */
|
|
113
|
+
const hubPort = (hub: IRuntimeNetworkVpnHub, transport: TRuntimeNetworkVpnTransport): number =>
|
|
114
|
+
({ quic: hub.quicPort })[transport];
|
|
101
115
|
|
|
102
116
|
const byEndpointId = <T extends { id: string }>(left: T, right: T): number =>
|
|
103
117
|
left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
104
118
|
|
|
105
119
|
const hubOf = (value: unknown): IRuntimeNetworkVpnHub => {
|
|
106
|
-
const item = networkRecord(value, ['clusterId', 'address', 'quicPort'
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
quicPort: port(item.quicPort), websocketPort: port(item.websocketPort) };
|
|
110
|
-
// A hub that bound no transport is not reachable, so it is not a hub a node could be sent to.
|
|
111
|
-
requireNetwork(runtimeNetworkAddressPlanContract.hubTransports.some(row => hubPort(hub, row.transport) !== null));
|
|
112
|
-
return hub;
|
|
120
|
+
const item = networkRecord(value, ['clusterId', 'address', 'quicPort']);
|
|
121
|
+
return { clusterId: networkId(item.clusterId), address: networkIpv4(item.address),
|
|
122
|
+
quicPort: networkCounter(item.quicPort, 65535) };
|
|
113
123
|
};
|
|
114
124
|
|
|
115
125
|
/**
|
|
116
|
-
* The platform endpoints one hub contributes to a protected authority: one per transport
|
|
117
|
-
* in id order. Every reader derives the same ids from this one function — the composition
|
|
118
|
-
* the authority, and the router selection that names which of them a node may dial.
|
|
126
|
+
* The platform endpoints one hub contributes to a protected authority: one per transport a managed
|
|
127
|
+
* hub serves, in id order. Every reader derives the same ids from this one function — the composition
|
|
128
|
+
* that seals the authority, and the router selection that names which of them a node may dial.
|
|
119
129
|
*/
|
|
120
130
|
export const runtimeNetworkVpnHubEndpoints = (hubValue: unknown): IRuntimeNetworkProtectedAuthority['platformEndpoints'] =>
|
|
121
131
|
networkSafe(() => {
|
|
122
132
|
const hub = hubOf(captureNetwork(hubValue));
|
|
123
133
|
return runtimeNetworkAddressPlanContract.hubTransports
|
|
124
|
-
.filter(row => hubPort(hub, row.transport) !== null)
|
|
125
134
|
.map(row => ({ id: `${hub.clusterId}:${row.endpointSuffix}`, address: hub.address,
|
|
126
|
-
protocol: row.protocol, port: hubPort(hub, row.transport)
|
|
135
|
+
protocol: row.protocol, port: hubPort(hub, row.transport) }))
|
|
127
136
|
.sort(byEndpointId);
|
|
128
137
|
});
|
|
129
138
|
|
|
@@ -154,9 +163,7 @@ const setsWithHubs = (plan: IRuntimeNetworkAddressPlan, hubs: IRuntimeNetworkVpn
|
|
|
154
163
|
|
|
155
164
|
const vpnOf = (value: unknown): IRuntimeNetworkAddressPlan['vpn'] => {
|
|
156
165
|
const item = networkRecord(value, ['controlPrefix', 'hubPrefix']);
|
|
157
|
-
|
|
158
|
-
requireNetwork(Number(controlPrefix.split('/')[1]) <= runtimeNetworkAddressPlanContract.maximumControlPrefixLength);
|
|
159
|
-
return { controlPrefix, hubPrefix: networkPrefix(item.hubPrefix) };
|
|
166
|
+
return { controlPrefix: runtimeNetworkVpnControlPrefix(item.controlPrefix), hubPrefix: networkPrefix(item.hubPrefix) };
|
|
160
167
|
};
|
|
161
168
|
|
|
162
169
|
/**
|
|
@@ -19,13 +19,6 @@ export interface IGetRuntimeManagedVpnCredentialResponse extends IGetRuntimeMana
|
|
|
19
19
|
transport: TRuntimeNetworkVpnTransport;
|
|
20
20
|
/** The cluster hub endpoint this node dials, named by the id its signed projection selected. */
|
|
21
21
|
hub: { id: string; address: string; port: number };
|
|
22
|
-
/**
|
|
23
|
-
* The TLS name the hub's WebSocket transport is dialled under, present exactly when `transport`
|
|
24
|
-
* is `websocket` and absent otherwise: QUIC is dialled on the address alone. It is the hub
|
|
25
|
-
* relay's own origin hostname, which the node cross-checks against the relay origin it stores,
|
|
26
|
-
* so a name the node does not already trust ends the handshake before any native effect.
|
|
27
|
-
*/
|
|
28
|
-
serverName?: string;
|
|
29
22
|
authorityId: string;
|
|
30
23
|
serverPublicKey: string;
|
|
31
24
|
clientPublicKey: string;
|
|
@@ -42,21 +35,6 @@ export const snapshotGetRuntimeManagedVpnCredentialRequest = (value: unknown): I
|
|
|
42
35
|
return { session: snapshotRuntimeSessionBinding(item.session), projection: networkReference(item.projection) };
|
|
43
36
|
});
|
|
44
37
|
|
|
45
|
-
/**
|
|
46
|
-
* The TLS name a WebSocket hub is dialled under: a lower-case domain name of at least two labels,
|
|
47
|
-
* which is what a relay origin's hostname is and what a certificate can be issued for. An address
|
|
48
|
-
* literal is not one, so a node can never be sent to a name nothing vouches for.
|
|
49
|
-
*/
|
|
50
|
-
const serverName = (value: unknown): string => {
|
|
51
|
-
requireNetwork(typeof value === 'string' && value.length <= 253);
|
|
52
|
-
const labels = (value as string).split('.');
|
|
53
|
-
// The last label starts with a letter, which is what makes this a name and not an address
|
|
54
|
-
// literal: a hub reached by address is reached over QUIC, which needs no name at all.
|
|
55
|
-
requireNetwork(labels.length >= 2 && labels.every(label => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label))
|
|
56
|
-
&& /^[a-z]/.test(labels[labels.length - 1]));
|
|
57
|
-
return value as string;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
38
|
const transportOf = (value: unknown): TRuntimeNetworkVpnTransport => {
|
|
61
39
|
const row = runtimeNetworkAddressPlanContract.hubTransports.find(candidate => candidate.transport === value);
|
|
62
40
|
requireNetwork(row); return row.transport;
|
|
@@ -64,14 +42,11 @@ const transportOf = (value: unknown): TRuntimeNetworkVpnTransport => {
|
|
|
64
42
|
|
|
65
43
|
export const snapshotGetRuntimeManagedVpnCredentialResponse = (value: unknown): IGetRuntimeManagedVpnCredentialResponse => networkSafe(() => {
|
|
66
44
|
const item = networkRecord(captureNetwork(value), ['session', 'projection', 'transport', 'hub', 'authorityId',
|
|
67
|
-
'serverPublicKey', 'clientPublicKey', 'clientPrivateKey', 'expiresAt']
|
|
68
|
-
const hub = networkRecord(item.hub, ['id', 'address', 'port'])
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
requireNetwork(Object.hasOwn(item, 'serverName') === (transport === 'websocket'));
|
|
72
|
-
return { session: snapshotRuntimeSessionBinding(item.session), projection: networkReference(item.projection), transport,
|
|
45
|
+
'serverPublicKey', 'clientPublicKey', 'clientPrivateKey', 'expiresAt']);
|
|
46
|
+
const hub = networkRecord(item.hub, ['id', 'address', 'port']);
|
|
47
|
+
return { session: snapshotRuntimeSessionBinding(item.session), projection: networkReference(item.projection),
|
|
48
|
+
transport: transportOf(item.transport),
|
|
73
49
|
hub: { id: networkId(hub.id), address: networkIpv4(hub.address), port: networkCounter(hub.port, 65535) },
|
|
74
|
-
...(transport === 'websocket' ? { serverName: serverName(item.serverName) } : {}),
|
|
75
50
|
authorityId: networkId(item.authorityId), serverPublicKey: networkNoiseKey(item.serverPublicKey),
|
|
76
51
|
clientPublicKey: networkNoiseKey(item.clientPublicKey), clientPrivateKey: networkNoiseKey(item.clientPrivateKey),
|
|
77
52
|
expiresAt: networkCounter(item.expiresAt) };
|