@serve.zone/interfaces 32.38.0 → 32.39.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 +19 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/networkcredential.d.ts +43 -1
- package/dist_ts/data/networkcredential.js +98 -12
- package/package.json +3 -3
- package/readme.md +26 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/networkcredential.ts +110 -13
package/changelog.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026-10-02 - 32.39.0
|
|
4
|
+
|
|
5
|
+
### Maintenance
|
|
6
|
+
|
|
7
|
+
- Update release tooling to tsbuild 5.1.0 and tsdoc 3.0.1 (Node.js types stay 26.6.2 and pnpm 12.6.0 until their seven-day release age).
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- A node's managed VPN membership now outlives its network projection.
|
|
12
|
+
`continuesRuntimeManagedVpnMembership(previous, next)` states what a membership is: one
|
|
13
|
+
projection lineage under one protected authority and one handoff lease (its node scope and
|
|
14
|
+
router incarnation), at a later generation (the same generation only as the identical projection). Workload, readiness, egress, router
|
|
15
|
+
selection and DNS window changes keep it; a protection bump, a fresh handoff or a new controller
|
|
16
|
+
epoch ends it. `bindGetRuntimeManagedVpnCredentialSuccessor` binds the credential for a successor
|
|
17
|
+
projection: the same session, transport, hub, authority and key pair as the held credential,
|
|
18
|
+
with only `projection` and `expiresAt` moved and the expiry inside the successor's effective DNS
|
|
19
|
+
window, which must cover the caller's time. A controller keeps a member and its key pair across a
|
|
20
|
+
projection change with it, so the node's hub session survives a workload change.
|
|
21
|
+
|
|
3
22
|
## 2026-09-30 - 32.38.0
|
|
4
23
|
|
|
5
24
|
### Features
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@serve.zone/interfaces',
|
|
6
|
-
version: '32.
|
|
6
|
+
version: '32.39.0',
|
|
7
7
|
description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLFNBQVM7SUFDbEIsV0FBVyxFQUFFLHVGQUF1RjtDQUNyRyxDQUFBIn0=
|
|
@@ -7,7 +7,12 @@ export interface IGetRuntimeManagedVpnCredentialRequest {
|
|
|
7
7
|
}
|
|
8
8
|
/** Secret response over the current authenticated physical peer only. Never
|
|
9
9
|
* persist, hash, log, or include it in a signed network projection. The native
|
|
10
|
-
* Noise owner validates and uses the keys; these shapes confer no TUN readiness.
|
|
10
|
+
* Noise owner validates and uses the keys; these shapes confer no TUN readiness.
|
|
11
|
+
*
|
|
12
|
+
* One response names one exact projection, but its key pair belongs to the node's managed VPN
|
|
13
|
+
* membership, which outlives a projection: a credential for a successor projection of the same
|
|
14
|
+
* membership (`continuesRuntimeManagedVpnMembership`) carries the same transport, hub, authority and
|
|
15
|
+
* keys, bound by `bindGetRuntimeManagedVpnCredentialSuccessor`. */
|
|
11
16
|
export interface IGetRuntimeManagedVpnCredentialResponse extends IGetRuntimeManagedVpnCredentialRequest {
|
|
12
17
|
transport: TRuntimeNetworkVpnTransport;
|
|
13
18
|
/** The cluster hub endpoint this node dials, named by the id its signed projection selected. */
|
|
@@ -32,6 +37,24 @@ export declare const snapshotGetRuntimeManagedVpnCredentialResponse: (value: unk
|
|
|
32
37
|
/** Content binding only. The caller must resolve the exact current projection
|
|
33
38
|
* and authenticate/fence the current physical peer before and after native work. */
|
|
34
39
|
export declare const bindGetRuntimeManagedVpnCredentialRequest: (value: unknown, projectionValue: unknown, trustedSessionValue: unknown) => Promise<IGetRuntimeManagedVpnCredentialRequest>;
|
|
40
|
+
/**
|
|
41
|
+
* Whether `next` continues the managed VPN membership `previous` belongs to: the same projection
|
|
42
|
+
* lineage (`id`), the same protected authority, and the same handoff lease, which names the node
|
|
43
|
+
* scope and carries the node's router incarnation. Both must hold a handoff, because a node without
|
|
44
|
+
* one is no member. `next` is `previous` itself or a later generation; an earlier one, or another
|
|
45
|
+
* projection under the same generation, continues nothing.
|
|
46
|
+
*
|
|
47
|
+
* Everything else a projection states may move under one membership: its workload endpoints, their
|
|
48
|
+
* readiness, observations and egress, its private networks, its router selection and its DNS window.
|
|
49
|
+
* Those are what the member's network carries, never who the member is. A protection bump, a fresh
|
|
50
|
+
* handoff (router reincarnation) or a new controller epoch ends the membership. Whether the hub a
|
|
51
|
+
* credential names is still selected is the credential's question
|
|
52
|
+
* (`bindGetRuntimeManagedVpnCredentialSuccessor`), not the membership's.
|
|
53
|
+
*
|
|
54
|
+
* Validates both complete projections and rejects either that does not validate; a valid pair that
|
|
55
|
+
* is not one membership answers `false`. It samples no clock and fences no session.
|
|
56
|
+
*/
|
|
57
|
+
export declare const continuesRuntimeManagedVpnMembership: (previousValue: unknown, nextValue: unknown) => Promise<boolean>;
|
|
35
58
|
/** Bind the secret response to the original request and the exact selected router
|
|
36
59
|
* endpoint of the credential's transport, inside the projection's effective DNS window. renewalValue is the
|
|
37
60
|
* bare lease renewal the caller already verified and admitted for this exact
|
|
@@ -39,3 +62,22 @@ export declare const bindGetRuntimeManagedVpnCredentialRequest: (value: unknown,
|
|
|
39
62
|
* this helper does not sample a clock, authenticate a transport, admit policy or
|
|
40
63
|
* prove a running managed connection. */
|
|
41
64
|
export declare const bindGetRuntimeManagedVpnCredentialResponse: (value: unknown, requestValue: unknown, projectionValue: unknown, trustedSessionValue: unknown, nowValue: unknown, renewalValue?: unknown | null) => Promise<IGetRuntimeManagedVpnCredentialResponse>;
|
|
65
|
+
/**
|
|
66
|
+
* Bind the credential a node holds under its successor projection: `value` names `projectionValue`
|
|
67
|
+
* and is bound exactly as `bindGetRuntimeManagedVpnCredentialResponse` binds the answer to
|
|
68
|
+
* `{ session: trustedSession, projection }`, inside that projection's effective DNS window
|
|
69
|
+
* (`renewalValue`, as there). It must also continue `previousValue`, the credential issued for
|
|
70
|
+
* `previousProjectionValue` over the same session: the two projections are one membership
|
|
71
|
+
* (`continuesRuntimeManagedVpnMembership`) and the transport, hub, authority, server key and client
|
|
72
|
+
* key pair are unchanged. Only `projection` and `expiresAt` move. The current effective window
|
|
73
|
+
* alone bounds the successor, so a membership whose current window does not cover the
|
|
74
|
+
* caller-qualified `nowValue` binds no successor; the held credential's own expiry is not
|
|
75
|
+
* consulted, because a renewal or a successor may have moved the window past it.
|
|
76
|
+
*
|
|
77
|
+
* The issuer binds the successor it states for a member, so the node's session and key pair survive
|
|
78
|
+
* a workload, readiness or policy change; the node binds a fresh answer with it to tell a successor,
|
|
79
|
+
* which keeps its live session, from a new membership, which replaces it. A previous credential for
|
|
80
|
+
* the same projection binds too, which is how a renewal moves the expiry. Content binding only, as
|
|
81
|
+
* for the response binder.
|
|
82
|
+
*/
|
|
83
|
+
export declare const bindGetRuntimeManagedVpnCredentialSuccessor: (value: unknown, previousValue: unknown, previousProjectionValue: unknown, projectionValue: unknown, trustedSessionValue: unknown, nowValue: unknown, renewalValue?: unknown | null) => Promise<IGetRuntimeManagedVpnCredentialResponse>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { runtimeNetworkAddressPlanContract } from './networkaddressplan.js';
|
|
2
2
|
import { snapshotRuntimeSessionBinding } from './runtimesession.js';
|
|
3
|
-
import { validateRuntimeNetworkProjection } from './networkprojection.js';
|
|
3
|
+
import { snapshotRuntimeNetworkProjection, validateRuntimeNetworkProjection } from './networkprojection.js';
|
|
4
4
|
import { snapshotRuntimeNetworkDnsLeaseRenewal, validateRuntimeNetworkDnsLeaseRenewal } from './networklease.js';
|
|
5
5
|
import { captureNetwork, failNetwork, networkAuthorityId, networkCounter, networkEffectiveDnsWindow, networkEqual, networkId, networkIpv4, networkNoiseKey, networkRecord, networkReference, networkReferenceOf, networkSafe, networkSameScope, requireNetwork } from '../private/networkcontract.js';
|
|
6
6
|
export const snapshotGetRuntimeManagedVpnCredentialRequest = (value) => networkSafe(() => {
|
|
@@ -41,6 +41,62 @@ export const bindGetRuntimeManagedVpnCredentialRequest = async (value, projectio
|
|
|
41
41
|
return failNetwork();
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* The membership rule of `continuesRuntimeManagedVpnMembership`, over projections already validated.
|
|
46
|
+
* A valid projection's handoff lease names the projection's own node scope, so one handoff
|
|
47
|
+
* reference is one node scope and the scope needs no comparison of its own.
|
|
48
|
+
*/
|
|
49
|
+
const continuesMembership = (previous, next) => previous.handoff !== null && next.handoff !== null && previous.id === next.id
|
|
50
|
+
&& networkEqual(networkReferenceOf(previous.protectedAuthority), networkReferenceOf(next.protectedAuthority))
|
|
51
|
+
&& networkEqual(networkReferenceOf(previous.handoff), networkReferenceOf(next.handoff))
|
|
52
|
+
&& (next.generation > previous.generation || networkEqual(networkReferenceOf(previous), networkReferenceOf(next)));
|
|
53
|
+
/** What a membership's credential keeps across its projections: everything but the projection and the expiry. */
|
|
54
|
+
const credentialIdentity = (value) => ({
|
|
55
|
+
transport: value.transport, hub: value.hub, authorityId: value.authorityId, serverPublicKey: value.serverPublicKey,
|
|
56
|
+
clientPublicKey: value.clientPublicKey, clientPrivateKey: value.clientPrivateKey,
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* Whether `next` continues the managed VPN membership `previous` belongs to: the same projection
|
|
60
|
+
* lineage (`id`), the same protected authority, and the same handoff lease, which names the node
|
|
61
|
+
* scope and carries the node's router incarnation. Both must hold a handoff, because a node without
|
|
62
|
+
* one is no member. `next` is `previous` itself or a later generation; an earlier one, or another
|
|
63
|
+
* projection under the same generation, continues nothing.
|
|
64
|
+
*
|
|
65
|
+
* Everything else a projection states may move under one membership: its workload endpoints, their
|
|
66
|
+
* readiness, observations and egress, its private networks, its router selection and its DNS window.
|
|
67
|
+
* Those are what the member's network carries, never who the member is. A protection bump, a fresh
|
|
68
|
+
* handoff (router reincarnation) or a new controller epoch ends the membership. Whether the hub a
|
|
69
|
+
* credential names is still selected is the credential's question
|
|
70
|
+
* (`bindGetRuntimeManagedVpnCredentialSuccessor`), not the membership's.
|
|
71
|
+
*
|
|
72
|
+
* Validates both complete projections and rejects either that does not validate; a valid pair that
|
|
73
|
+
* is not one membership answers `false`. It samples no clock and fences no session.
|
|
74
|
+
*/
|
|
75
|
+
export const continuesRuntimeManagedVpnMembership = async (previousValue, nextValue) => {
|
|
76
|
+
let previous, next;
|
|
77
|
+
try {
|
|
78
|
+
const previousInput = snapshotRuntimeNetworkProjection(previousValue), nextInput = snapshotRuntimeNetworkProjection(nextValue);
|
|
79
|
+
previous = await validateRuntimeNetworkProjection(previousInput);
|
|
80
|
+
next = await validateRuntimeNetworkProjection(nextInput);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return failNetwork();
|
|
84
|
+
}
|
|
85
|
+
return continuesMembership(previous, next);
|
|
86
|
+
};
|
|
87
|
+
/** The response binding over inputs already captured and validated. */
|
|
88
|
+
const bindResponse = (response, request, projection, renewal, trusted, now) => {
|
|
89
|
+
const window = networkEffectiveDnsWindow(projection, renewal);
|
|
90
|
+
bindRequest(request, projection, trusted);
|
|
91
|
+
requireNetwork(networkEqual(response.session, request.session) && networkEqual(response.projection, request.projection));
|
|
92
|
+
requireNetwork(projection.router?.platformEndpointIds.includes(response.hub.id));
|
|
93
|
+
const endpoint = projection.protectedAuthority.platformEndpoints.find(item => item.id === response.hub.id);
|
|
94
|
+
const expected = runtimeNetworkAddressPlanContract.hubTransports.find(row => row.transport === response.transport);
|
|
95
|
+
requireNetwork(endpoint && expected && endpoint.protocol === expected.protocol
|
|
96
|
+
&& endpoint.address === response.hub.address && endpoint.port === response.hub.port);
|
|
97
|
+
requireNetwork(window.notBefore <= now && now < response.expiresAt && response.expiresAt <= window.expiresAt);
|
|
98
|
+
return response;
|
|
99
|
+
};
|
|
44
100
|
/** Bind the secret response to the original request and the exact selected router
|
|
45
101
|
* endpoint of the credential's transport, inside the projection's effective DNS window. renewalValue is the
|
|
46
102
|
* bare lease renewal the caller already verified and admitted for this exact
|
|
@@ -55,19 +111,49 @@ export const bindGetRuntimeManagedVpnCredentialResponse = async (value, requestV
|
|
|
55
111
|
const renewalInput = renewalValue === null ? null : snapshotRuntimeNetworkDnsLeaseRenewal(renewalValue);
|
|
56
112
|
const projection = await validateRuntimeNetworkProjection(projectionValue);
|
|
57
113
|
const renewal = renewalInput === null ? null : await validateRuntimeNetworkDnsLeaseRenewal(renewalInput);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
114
|
+
return bindResponse(response, request, projection, renewal, trusted, now);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return failNetwork();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Bind the credential a node holds under its successor projection: `value` names `projectionValue`
|
|
122
|
+
* and is bound exactly as `bindGetRuntimeManagedVpnCredentialResponse` binds the answer to
|
|
123
|
+
* `{ session: trustedSession, projection }`, inside that projection's effective DNS window
|
|
124
|
+
* (`renewalValue`, as there). It must also continue `previousValue`, the credential issued for
|
|
125
|
+
* `previousProjectionValue` over the same session: the two projections are one membership
|
|
126
|
+
* (`continuesRuntimeManagedVpnMembership`) and the transport, hub, authority, server key and client
|
|
127
|
+
* key pair are unchanged. Only `projection` and `expiresAt` move. The current effective window
|
|
128
|
+
* alone bounds the successor, so a membership whose current window does not cover the
|
|
129
|
+
* caller-qualified `nowValue` binds no successor; the held credential's own expiry is not
|
|
130
|
+
* consulted, because a renewal or a successor may have moved the window past it.
|
|
131
|
+
*
|
|
132
|
+
* The issuer binds the successor it states for a member, so the node's session and key pair survive
|
|
133
|
+
* a workload, readiness or policy change; the node binds a fresh answer with it to tell a successor,
|
|
134
|
+
* which keeps its live session, from a new membership, which replaces it. A previous credential for
|
|
135
|
+
* the same projection binds too, which is how a renewal moves the expiry. Content binding only, as
|
|
136
|
+
* for the response binder.
|
|
137
|
+
*/
|
|
138
|
+
export const bindGetRuntimeManagedVpnCredentialSuccessor = async (value, previousValue, previousProjectionValue, projectionValue, trustedSessionValue, nowValue, renewalValue = null) => {
|
|
139
|
+
try {
|
|
140
|
+
const response = snapshotGetRuntimeManagedVpnCredentialResponse(value);
|
|
141
|
+
const previous = snapshotGetRuntimeManagedVpnCredentialResponse(previousValue);
|
|
142
|
+
const previousInput = snapshotRuntimeNetworkProjection(previousProjectionValue);
|
|
143
|
+
const projectionInput = snapshotRuntimeNetworkProjection(projectionValue);
|
|
144
|
+
const trusted = snapshotRuntimeSessionBinding(trustedSessionValue), now = networkCounter(nowValue);
|
|
145
|
+
const renewalInput = renewalValue === null ? null : snapshotRuntimeNetworkDnsLeaseRenewal(renewalValue);
|
|
146
|
+
const previousProjection = await validateRuntimeNetworkProjection(previousInput);
|
|
147
|
+
const projection = await validateRuntimeNetworkProjection(projectionInput);
|
|
148
|
+
const renewal = renewalInput === null ? null : await validateRuntimeNetworkDnsLeaseRenewal(renewalInput);
|
|
149
|
+
requireNetwork(continuesMembership(previousProjection, projection));
|
|
150
|
+
requireNetwork(networkEqual(previous.session, trusted)
|
|
151
|
+
&& networkEqual(previous.projection, networkReferenceOf(previousProjection)));
|
|
152
|
+
requireNetwork(networkEqual(credentialIdentity(previous), credentialIdentity(response)));
|
|
153
|
+
return bindResponse(response, { session: trusted, projection: networkReferenceOf(projection) }, projection, renewal, trusted, now);
|
|
68
154
|
}
|
|
69
155
|
catch {
|
|
70
156
|
return failNetwork();
|
|
71
157
|
}
|
|
72
158
|
};
|
|
73
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
159
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmV0d29ya2NyZWRlbnRpYWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL25ldHdvcmtjcmVkZW50aWFsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxpQ0FBaUMsRUFBb0MsTUFBTSx5QkFBeUIsQ0FBQztBQUM5RyxPQUFPLEVBQUUsNkJBQTZCLEVBQStCLE1BQU0scUJBQXFCLENBQUM7QUFDakcsT0FBTyxFQUFFLGdDQUFnQyxFQUFFLGdDQUFnQyxFQUN6QyxNQUFNLHdCQUF3QixDQUFDO0FBQ2pFLE9BQU8sRUFBRSxxQ0FBcUMsRUFBRSxxQ0FBcUMsRUFDOUMsTUFBTSxtQkFBbUIsQ0FBQztBQUNqRSxPQUFPLEVBQUUsY0FBYyxFQUFFLFdBQVcsRUFBRSxrQkFBa0IsRUFBRSxjQUFjLEVBQUUseUJBQXlCLEVBQUUsWUFBWSxFQUMvRyxTQUFTLEVBQUUsV0FBVyxFQUFFLGVBQWUsRUFBRSxhQUFhLEVBQUUsZ0JBQWdCLEVBQUUsa0JBQWtCLEVBQUUsV0FBVyxFQUN6RyxnQkFBZ0IsRUFBRSxjQUFjLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQStCMUUsTUFBTSxDQUFDLE1BQU0sNkNBQTZDLEdBQUcsQ0FBQyxLQUFjLEVBQTBDLEVBQUUsQ0FBQyxXQUFXLENBQUMsR0FBRyxFQUFFO0lBQ3hJLE1BQU0sSUFBSSxHQUFHLGFBQWEsQ0FBQyxjQUFjLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxTQUFTLEVBQUUsWUFBWSxDQUFDLENBQUMsQ0FBQztJQUM3RSxPQUFPLEVBQUUsT0FBTyxFQUFFLDZCQUE2QixDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxVQUFVLEVBQUUsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7QUFDakgsQ0FBQyxDQUFDLENBQUM7QUFFSCxNQUFNLFdBQVcsR0FBRyxDQUFDLEtBQWMsRUFBK0IsRUFBRTtJQUNsRSxNQUFNLEdBQUcsR0FBRyxpQ0FBaUMsQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsU0FBUyxDQUFDLFNBQVMsS0FBSyxLQUFLLENBQUMsQ0FBQztJQUM3RyxjQUFjLENBQUMsR0FBRyxDQUFDLENBQUM7SUFBQyxPQUFPLEdBQUcsQ0FBQyxTQUFTLENBQUM7QUFDNUMsQ0FBQyxDQUFDO0FBRUYsTUFBTSxDQUFDLE1BQU0sOENBQThDLEdBQUcsQ0FBQyxLQUFjLEVBQTJDLEVBQUUsQ0FBQyxXQUFXLENBQUMsR0FBRyxFQUFFO0lBQzFJLE1BQU0sSUFBSSxHQUFHLGFBQWEsQ0FBQyxjQUFjLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxTQUFTLEVBQUUsWUFBWSxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsYUFBYTtRQUMzRyxpQkFBaUIsRUFBRSxpQkFBaUIsRUFBRSxrQkFBa0IsRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDO0lBQzFFLE1BQU0sR0FBRyxHQUFHLGFBQWEsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUMsSUFBSSxFQUFFLFNBQVMsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBQy9ELE9BQU8sRUFBRSxPQUFPLEVBQUUsNkJBQTZCLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxFQUFFLFVBQVUsRUFBRSxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDO1FBQzFHLFNBQVMsRUFBRSxXQUFXLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQztRQUN0QyxHQUFHLEVBQUUsRUFBRSxFQUFFLEVBQUUsU0FBUyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsRUFBRSxPQUFPLEVBQUUsV0FBVyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsRUFBRSxJQUFJLEVBQUUsY0FBYyxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLEVBQUU7UUFDeEcsV0FBVyxFQUFFLGtCQUFrQixDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsRUFBRSxlQUFlLEVBQUUsZUFBZSxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUM7UUFDekcsZUFBZSxFQUFFLGVBQWUsQ0FBQyxJQUFJLENBQUMsZUFBZSxDQUFDLEVBQUUsZ0JBQWdCLEVBQUUsZUFBZSxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQztRQUNoSCxTQUFTLEVBQUUsY0FBYyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDO0FBQ2hELENBQUMsQ0FBQyxDQUFDO0FBRUgsTUFBTSxXQUFXLEdBQUcsQ0FBQyxPQUErQyxFQUNsRSxVQUFxQyxFQUFFLE9BQStCLEVBQVEsRUFBRTtJQUNoRixjQUFjLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLElBQUksWUFBWSxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsa0JBQWtCLENBQUMsVUFBVSxDQUFDLENBQUM7V0FDcEgsZ0JBQWdCLENBQUMsVUFBVSxFQUFFLE9BQU8sQ0FBQyxJQUFJLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUNwRSxDQUFDLENBQUM7QUFFRjtvRkFDb0Y7QUFDcEYsTUFBTSxDQUFDLE1BQU0seUNBQXlDLEdBQUcsS0FBSyxFQUFFLEtBQWMsRUFBRSxlQUF3QixFQUN0RyxtQkFBNEIsRUFBbUQsRUFBRTtJQUNqRixJQUFJLENBQUM7UUFDSCxNQUFNLE9BQU8sR0FBRyw2Q0FBNkMsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUNyRSxNQUFNLE9BQU8sR0FBRyw2QkFBNkIsQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO1FBQ25FLE1BQU0sVUFBVSxHQUFHLE1BQU0sZ0NBQWdDLENBQUMsZUFBZSxDQUFDLENBQUM7UUFDM0UsV0FBVyxDQUFDLE9BQU8sRUFBRSxVQUFVLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDMUMsT0FBTyxPQUFPLENBQUM7SUFDakIsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUFDLE9BQU8sV0FBVyxFQUFFLENBQUM7SUFBQyxDQUFDO0FBQ25DLENBQUMsQ0FBQztBQUVGOzs7O0dBSUc7QUFDSCxNQUFNLG1CQUFtQixHQUFHLENBQUMsUUFBbUMsRUFBRSxJQUErQixFQUFXLEVBQUUsQ0FDNUcsUUFBUSxDQUFDLE9BQU8sS0FBSyxJQUFJLElBQUksSUFBSSxDQUFDLE9BQU8sS0FBSyxJQUFJLElBQUksUUFBUSxDQUFDLEVBQUUsS0FBSyxJQUFJLENBQUMsRUFBRTtPQUMxRSxZQUFZLENBQUMsa0JBQWtCLENBQUMsUUFBUSxDQUFDLGtCQUFrQixDQUFDLEVBQUUsa0JBQWtCLENBQUMsSUFBSSxDQUFDLGtCQUFrQixDQUFDLENBQUM7T0FDMUcsWUFBWSxDQUFDLGtCQUFrQixDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsRUFBRSxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7T0FDcEYsQ0FBQyxJQUFJLENBQUMsVUFBVSxHQUFHLFFBQVEsQ0FBQyxVQUFVLElBQUksWUFBWSxDQUFDLGtCQUFrQixDQUFDLFFBQVEsQ0FBQyxFQUFFLGtCQUFrQixDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUVySCxpSEFBaUg7QUFDakgsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLEtBQThDLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDOUUsU0FBUyxFQUFFLEtBQUssQ0FBQyxTQUFTLEVBQUUsR0FBRyxFQUFFLEtBQUssQ0FBQyxHQUFHLEVBQUUsV0FBVyxFQUFFLEtBQUssQ0FBQyxXQUFXLEVBQUUsZUFBZSxFQUFFLEtBQUssQ0FBQyxlQUFlO0lBQ2xILGVBQWUsRUFBRSxLQUFLLENBQUMsZUFBZSxFQUFFLGdCQUFnQixFQUFFLEtBQUssQ0FBQyxnQkFBZ0I7Q0FDakYsQ0FBQyxDQUFDO0FBRUg7Ozs7Ozs7Ozs7Ozs7Ozs7R0FnQkc7QUFDSCxNQUFNLENBQUMsTUFBTSxvQ0FBb0MsR0FBRyxLQUFLLEVBQUUsYUFBc0IsRUFBRSxTQUFrQixFQUFvQixFQUFFO0lBQ3pILElBQUksUUFBbUMsRUFBRSxJQUErQixDQUFDO0lBQ3pFLElBQUksQ0FBQztRQUNILE1BQU0sYUFBYSxHQUFHLGdDQUFnQyxDQUFDLGFBQWEsQ0FBQyxFQUFFLFNBQVMsR0FBRyxnQ0FBZ0MsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUMvSCxRQUFRLEdBQUcsTUFBTSxnQ0FBZ0MsQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUNqRSxJQUFJLEdBQUcsTUFBTSxnQ0FBZ0MsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUMzRCxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQUMsT0FBTyxXQUFXLEVBQUUsQ0FBQztJQUFDLENBQUM7SUFDakMsT0FBTyxtQkFBbUIsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDN0MsQ0FBQyxDQUFDO0FBRUYsdUVBQXVFO0FBQ3ZFLE1BQU0sWUFBWSxHQUFHLENBQUMsUUFBaUQsRUFBRSxPQUErQyxFQUN0SCxVQUFxQyxFQUFFLE9BQThDLEVBQUUsT0FBK0IsRUFDdEgsR0FBVyxFQUEyQyxFQUFFO0lBQ3hELE1BQU0sTUFBTSxHQUFHLHlCQUF5QixDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUM5RCxXQUFXLENBQUMsT0FBTyxFQUFFLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUMxQyxjQUFjLENBQUMsWUFBWSxDQUFDLFFBQVEsQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLFlBQVksQ0FBQyxRQUFRLENBQUMsVUFBVSxFQUFFLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDO0lBQ3pILGNBQWMsQ0FBQyxVQUFVLENBQUMsTUFBTSxFQUFFLG1CQUFtQixDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDakYsTUFBTSxRQUFRLEdBQUcsVUFBVSxDQUFDLGtCQUFrQixDQUFDLGlCQUFpQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxFQUFFLEtBQUssUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUMzRyxNQUFNLFFBQVEsR0FBRyxpQ0FBaUMsQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLFNBQVMsS0FBSyxRQUFRLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDbkgsY0FBYyxDQUFDLFFBQVEsSUFBSSxRQUFRLElBQUksUUFBUSxDQUFDLFFBQVEsS0FBSyxRQUFRLENBQUMsUUFBUTtXQUN6RSxRQUFRLENBQUMsT0FBTyxLQUFLLFFBQVEsQ0FBQyxHQUFHLENBQUMsT0FBTyxJQUFJLFFBQVEsQ0FBQyxJQUFJLEtBQUssUUFBUSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUN2RixjQUFjLENBQUMsTUFBTSxDQUFDLFNBQVMsSUFBSSxHQUFHLElBQUksR0FBRyxHQUFHLFFBQVEsQ0FBQyxTQUFTLElBQUksUUFBUSxDQUFDLFNBQVMsSUFBSSxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDOUcsT0FBTyxRQUFRLENBQUM7QUFDbEIsQ0FBQyxDQUFDO0FBRUY7Ozs7O3lDQUt5QztBQUN6QyxNQUFNLENBQUMsTUFBTSwwQ0FBMEMsR0FBRyxLQUFLLEVBQUUsS0FBYyxFQUFFLFlBQXFCLEVBQ3BHLGVBQXdCLEVBQUUsbUJBQTRCLEVBQUUsUUFBaUIsRUFDekUsZUFBK0IsSUFBSSxFQUFvRCxFQUFFO0lBQ3pGLElBQUksQ0FBQztRQUNILE1BQU0sUUFBUSxHQUFHLDhDQUE4QyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3ZFLE1BQU0sT0FBTyxHQUFHLDZDQUE2QyxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQzVFLE1BQU0sT0FBTyxHQUFHLDZCQUE2QixDQUFDLG1CQUFtQixDQUFDLEVBQUUsR0FBRyxHQUFHLGNBQWMsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUNuRyxNQUFNLFlBQVksR0FBRyxZQUFZLEtBQUssSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLHFDQUFxQyxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQ3hHLE1BQU0sVUFBVSxHQUFHLE1BQU0sZ0NBQWdDLENBQUMsZUFBZSxDQUFDLENBQUM7UUFDM0UsTUFBTSxPQUFPLEdBQUcsWUFBWSxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxNQUFNLHFDQUFxQyxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQ3pHLE9BQU8sWUFBWSxDQUFDLFFBQVEsRUFBRSxPQUFPLEVBQUUsVUFBVSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDNUUsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUFDLE9BQU8sV0FBVyxFQUFFLENBQUM7SUFBQyxDQUFDO0FBQ25DLENBQUMsQ0FBQztBQUVGOzs7Ozs7Ozs7Ozs7Ozs7OztHQWlCRztBQUNILE1BQU0sQ0FBQyxNQUFNLDJDQUEyQyxHQUFHLEtBQUssRUFBRSxLQUFjLEVBQUUsYUFBc0IsRUFDdEcsdUJBQWdDLEVBQUUsZUFBd0IsRUFBRSxtQkFBNEIsRUFBRSxRQUFpQixFQUMzRyxlQUErQixJQUFJLEVBQW9ELEVBQUU7SUFDekYsSUFBSSxDQUFDO1FBQ0gsTUFBTSxRQUFRLEdBQUcsOENBQThDLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDdkUsTUFBTSxRQUFRLEdBQUcsOENBQThDLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDL0UsTUFBTSxhQUFhLEdBQUcsZ0NBQWdDLENBQUMsdUJBQXVCLENBQUMsQ0FBQztRQUNoRixNQUFNLGVBQWUsR0FBRyxnQ0FBZ0MsQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUMxRSxNQUFNLE9BQU8sR0FBRyw2QkFBNkIsQ0FBQyxtQkFBbUIsQ0FBQyxFQUFFLEdBQUcsR0FBRyxjQUFjLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDbkcsTUFBTSxZQUFZLEdBQUcsWUFBWSxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxxQ0FBcUMsQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUN4RyxNQUFNLGtCQUFrQixHQUFHLE1BQU0sZ0NBQWdDLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDakYsTUFBTSxVQUFVLEdBQUcsTUFBTSxnQ0FBZ0MsQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUMzRSxNQUFNLE9BQU8sR0FBRyxZQUFZLEtBQUssSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLE1BQU0scUNBQXFDLENBQUMsWUFBWSxDQUFDLENBQUM7UUFDekcsY0FBYyxDQUFDLG1CQUFtQixDQUFDLGtCQUFrQixFQUFFLFVBQVUsQ0FBQyxDQUFDLENBQUM7UUFDcEUsY0FBYyxDQUFDLFlBQVksQ0FBQyxRQUFRLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQztlQUNqRCxZQUFZLENBQUMsUUFBUSxDQUFDLFVBQVUsRUFBRSxrQkFBa0IsQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNoRixjQUFjLENBQUMsWUFBWSxDQUFDLGtCQUFrQixDQUFDLFFBQVEsQ0FBQyxFQUFFLGtCQUFrQixDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUN6RixPQUFPLFlBQVksQ0FBQyxRQUFRLEVBQUUsRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxrQkFBa0IsQ0FBQyxVQUFVLENBQUMsRUFBRSxFQUFFLFVBQVUsRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLEdBQUcsQ0FBQyxDQUFDO0lBQ3JJLENBQUM7SUFBQyxNQUFNLENBQUM7UUFBQyxPQUFPLFdBQVcsRUFBRSxDQUFDO0lBQUMsQ0FBQztBQUNuQyxDQUFDLENBQUMifQ==
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serve.zone/interfaces",
|
|
3
|
-
"version": "32.
|
|
3
|
+
"version": "32.39.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
|
|
6
6
|
"exports": {
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"@tsclass/tsclass": "^9.5.1"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@git.zone/tsbuild": "^5.
|
|
31
|
-
"@git.zone/tsdoc": "^
|
|
30
|
+
"@git.zone/tsbuild": "^5.1.0",
|
|
31
|
+
"@git.zone/tsdoc": "^3.0.1",
|
|
32
32
|
"@git.zone/tsrun": "^3.0.2",
|
|
33
33
|
"@git.zone/tstest": "6.3.2",
|
|
34
34
|
"@types/node": "26.6.2"
|
package/readme.md
CHANGED
|
@@ -728,6 +728,32 @@ Expiry must be after the caller-qualified time and no later than the referenced
|
|
|
728
728
|
projection's effective DNS window (see [DNS Lease Renewals](#dns-lease-renewals));
|
|
729
729
|
acceptance before that window starts rejects.
|
|
730
730
|
|
|
731
|
+
A credential names one projection, but its key pair belongs to the node's managed VPN
|
|
732
|
+
membership, which outlives it. Every workload add, removal, readiness change or policy change
|
|
733
|
+
signs a new projection generation; none of them changes who the member is.
|
|
734
|
+
`continuesRuntimeManagedVpnMembership(previous, next)` is that rule: `next` continues the
|
|
735
|
+
membership when it is the same projection lineage (`id`) under the same protected authority and
|
|
736
|
+
the same handoff lease (which names the node scope and carries the router incarnation), and is
|
|
737
|
+
`previous` itself or a later generation. A protection bump, a fresh handoff (router
|
|
738
|
+
reincarnation) or a new controller epoch is a new membership; an earlier generation or another
|
|
739
|
+
projection of the same generation continues nothing. It validates both projections and rejects
|
|
740
|
+
one that does not validate.
|
|
741
|
+
|
|
742
|
+
`bindGetRuntimeManagedVpnCredentialSuccessor(response, previous, previousProjection, projection,
|
|
743
|
+
trustedSession, now, renewal = null)` binds the credential for the successor. `response` is bound
|
|
744
|
+
exactly as the response binder binds an answer to `{ session: trustedSession, projection }`, so
|
|
745
|
+
the hub must still be selected and the expiry stays inside the successor's effective DNS window.
|
|
746
|
+
It must also continue `previous`, the credential issued for `previousProjection` over the same
|
|
747
|
+
session: the two projections are one membership and the transport, hub, authority, server key and
|
|
748
|
+
client key pair are unchanged. Only `projection` and `expiresAt` move. The current effective
|
|
749
|
+
window is the one limit: a membership whose current window does not cover `now` binds no
|
|
750
|
+
successor, while the held credential's own expiry is not consulted, because a renewal or a
|
|
751
|
+
successor may already have moved the window past it. The issuer binds the successor it states
|
|
752
|
+
for a member with it, so the node keeps its key pair and its hub session across a projection
|
|
753
|
+
change; a node binds a fresh answer with it to tell a successor, which keeps the live session,
|
|
754
|
+
from a new membership, which replaces it. A previous credential for the same projection binds as
|
|
755
|
+
its own successor, which is how a DNS lease renewal moves the expiry.
|
|
756
|
+
|
|
731
757
|
These helpers perform content validation. Callers authenticate and fence the
|
|
732
758
|
current physical peer and projection before and after native work, qualify time
|
|
733
759
|
independently, and retain ownership through revocation and shutdown. The native
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { IRuntimeAuthorityReference } from './runtimeassignment.js';
|
|
2
2
|
import { runtimeNetworkAddressPlanContract, type TRuntimeNetworkVpnTransport } from './networkaddressplan.js';
|
|
3
3
|
import { snapshotRuntimeSessionBinding, type IRuntimeSessionBinding } from './runtimesession.js';
|
|
4
|
-
import { validateRuntimeNetworkProjection,
|
|
5
|
-
|
|
4
|
+
import { snapshotRuntimeNetworkProjection, validateRuntimeNetworkProjection,
|
|
5
|
+
type IRuntimeNetworkProjection } from './networkprojection.js';
|
|
6
|
+
import { snapshotRuntimeNetworkDnsLeaseRenewal, validateRuntimeNetworkDnsLeaseRenewal,
|
|
7
|
+
type IRuntimeNetworkDnsLeaseRenewal } from './networklease.js';
|
|
6
8
|
import { captureNetwork, failNetwork, networkAuthorityId, networkCounter, networkEffectiveDnsWindow, networkEqual,
|
|
7
9
|
networkId, networkIpv4, networkNoiseKey, networkRecord, networkReference, networkReferenceOf, networkSafe,
|
|
8
10
|
networkSameScope, requireNetwork } from '../private/networkcontract.js';
|
|
@@ -14,7 +16,12 @@ export interface IGetRuntimeManagedVpnCredentialRequest {
|
|
|
14
16
|
|
|
15
17
|
/** Secret response over the current authenticated physical peer only. Never
|
|
16
18
|
* persist, hash, log, or include it in a signed network projection. The native
|
|
17
|
-
* Noise owner validates and uses the keys; these shapes confer no TUN readiness.
|
|
19
|
+
* Noise owner validates and uses the keys; these shapes confer no TUN readiness.
|
|
20
|
+
*
|
|
21
|
+
* One response names one exact projection, but its key pair belongs to the node's managed VPN
|
|
22
|
+
* membership, which outlives a projection: a credential for a successor projection of the same
|
|
23
|
+
* membership (`continuesRuntimeManagedVpnMembership`) carries the same transport, hub, authority and
|
|
24
|
+
* keys, bound by `bindGetRuntimeManagedVpnCredentialSuccessor`. */
|
|
18
25
|
export interface IGetRuntimeManagedVpnCredentialResponse extends IGetRuntimeManagedVpnCredentialRequest {
|
|
19
26
|
transport: TRuntimeNetworkVpnTransport;
|
|
20
27
|
/** The cluster hub endpoint this node dials, named by the id its signed projection selected. */
|
|
@@ -72,6 +79,66 @@ export const bindGetRuntimeManagedVpnCredentialRequest = async (value: unknown,
|
|
|
72
79
|
} catch { return failNetwork(); }
|
|
73
80
|
};
|
|
74
81
|
|
|
82
|
+
/**
|
|
83
|
+
* The membership rule of `continuesRuntimeManagedVpnMembership`, over projections already validated.
|
|
84
|
+
* A valid projection's handoff lease names the projection's own node scope, so one handoff
|
|
85
|
+
* reference is one node scope and the scope needs no comparison of its own.
|
|
86
|
+
*/
|
|
87
|
+
const continuesMembership = (previous: IRuntimeNetworkProjection, next: IRuntimeNetworkProjection): boolean =>
|
|
88
|
+
previous.handoff !== null && next.handoff !== null && previous.id === next.id
|
|
89
|
+
&& networkEqual(networkReferenceOf(previous.protectedAuthority), networkReferenceOf(next.protectedAuthority))
|
|
90
|
+
&& networkEqual(networkReferenceOf(previous.handoff), networkReferenceOf(next.handoff))
|
|
91
|
+
&& (next.generation > previous.generation || networkEqual(networkReferenceOf(previous), networkReferenceOf(next)));
|
|
92
|
+
|
|
93
|
+
/** What a membership's credential keeps across its projections: everything but the projection and the expiry. */
|
|
94
|
+
const credentialIdentity = (value: IGetRuntimeManagedVpnCredentialResponse) => ({
|
|
95
|
+
transport: value.transport, hub: value.hub, authorityId: value.authorityId, serverPublicKey: value.serverPublicKey,
|
|
96
|
+
clientPublicKey: value.clientPublicKey, clientPrivateKey: value.clientPrivateKey,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Whether `next` continues the managed VPN membership `previous` belongs to: the same projection
|
|
101
|
+
* lineage (`id`), the same protected authority, and the same handoff lease, which names the node
|
|
102
|
+
* scope and carries the node's router incarnation. Both must hold a handoff, because a node without
|
|
103
|
+
* one is no member. `next` is `previous` itself or a later generation; an earlier one, or another
|
|
104
|
+
* projection under the same generation, continues nothing.
|
|
105
|
+
*
|
|
106
|
+
* Everything else a projection states may move under one membership: its workload endpoints, their
|
|
107
|
+
* readiness, observations and egress, its private networks, its router selection and its DNS window.
|
|
108
|
+
* Those are what the member's network carries, never who the member is. A protection bump, a fresh
|
|
109
|
+
* handoff (router reincarnation) or a new controller epoch ends the membership. Whether the hub a
|
|
110
|
+
* credential names is still selected is the credential's question
|
|
111
|
+
* (`bindGetRuntimeManagedVpnCredentialSuccessor`), not the membership's.
|
|
112
|
+
*
|
|
113
|
+
* Validates both complete projections and rejects either that does not validate; a valid pair that
|
|
114
|
+
* is not one membership answers `false`. It samples no clock and fences no session.
|
|
115
|
+
*/
|
|
116
|
+
export const continuesRuntimeManagedVpnMembership = async (previousValue: unknown, nextValue: unknown): Promise<boolean> => {
|
|
117
|
+
let previous: IRuntimeNetworkProjection, next: IRuntimeNetworkProjection;
|
|
118
|
+
try {
|
|
119
|
+
const previousInput = snapshotRuntimeNetworkProjection(previousValue), nextInput = snapshotRuntimeNetworkProjection(nextValue);
|
|
120
|
+
previous = await validateRuntimeNetworkProjection(previousInput);
|
|
121
|
+
next = await validateRuntimeNetworkProjection(nextInput);
|
|
122
|
+
} catch { return failNetwork(); }
|
|
123
|
+
return continuesMembership(previous, next);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/** The response binding over inputs already captured and validated. */
|
|
127
|
+
const bindResponse = (response: IGetRuntimeManagedVpnCredentialResponse, request: IGetRuntimeManagedVpnCredentialRequest,
|
|
128
|
+
projection: IRuntimeNetworkProjection, renewal: IRuntimeNetworkDnsLeaseRenewal | null, trusted: IRuntimeSessionBinding,
|
|
129
|
+
now: number): IGetRuntimeManagedVpnCredentialResponse => {
|
|
130
|
+
const window = networkEffectiveDnsWindow(projection, renewal);
|
|
131
|
+
bindRequest(request, projection, trusted);
|
|
132
|
+
requireNetwork(networkEqual(response.session, request.session) && networkEqual(response.projection, request.projection));
|
|
133
|
+
requireNetwork(projection.router?.platformEndpointIds.includes(response.hub.id));
|
|
134
|
+
const endpoint = projection.protectedAuthority.platformEndpoints.find(item => item.id === response.hub.id);
|
|
135
|
+
const expected = runtimeNetworkAddressPlanContract.hubTransports.find(row => row.transport === response.transport);
|
|
136
|
+
requireNetwork(endpoint && expected && endpoint.protocol === expected.protocol
|
|
137
|
+
&& endpoint.address === response.hub.address && endpoint.port === response.hub.port);
|
|
138
|
+
requireNetwork(window.notBefore <= now && now < response.expiresAt && response.expiresAt <= window.expiresAt);
|
|
139
|
+
return response;
|
|
140
|
+
};
|
|
141
|
+
|
|
75
142
|
/** Bind the secret response to the original request and the exact selected router
|
|
76
143
|
* endpoint of the credential's transport, inside the projection's effective DNS window. renewalValue is the
|
|
77
144
|
* bare lease renewal the caller already verified and admitted for this exact
|
|
@@ -88,15 +155,45 @@ export const bindGetRuntimeManagedVpnCredentialResponse = async (value: unknown,
|
|
|
88
155
|
const renewalInput = renewalValue === null ? null : snapshotRuntimeNetworkDnsLeaseRenewal(renewalValue);
|
|
89
156
|
const projection = await validateRuntimeNetworkProjection(projectionValue);
|
|
90
157
|
const renewal = renewalInput === null ? null : await validateRuntimeNetworkDnsLeaseRenewal(renewalInput);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
158
|
+
return bindResponse(response, request, projection, renewal, trusted, now);
|
|
159
|
+
} catch { return failNetwork(); }
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Bind the credential a node holds under its successor projection: `value` names `projectionValue`
|
|
164
|
+
* and is bound exactly as `bindGetRuntimeManagedVpnCredentialResponse` binds the answer to
|
|
165
|
+
* `{ session: trustedSession, projection }`, inside that projection's effective DNS window
|
|
166
|
+
* (`renewalValue`, as there). It must also continue `previousValue`, the credential issued for
|
|
167
|
+
* `previousProjectionValue` over the same session: the two projections are one membership
|
|
168
|
+
* (`continuesRuntimeManagedVpnMembership`) and the transport, hub, authority, server key and client
|
|
169
|
+
* key pair are unchanged. Only `projection` and `expiresAt` move. The current effective window
|
|
170
|
+
* alone bounds the successor, so a membership whose current window does not cover the
|
|
171
|
+
* caller-qualified `nowValue` binds no successor; the held credential's own expiry is not
|
|
172
|
+
* consulted, because a renewal or a successor may have moved the window past it.
|
|
173
|
+
*
|
|
174
|
+
* The issuer binds the successor it states for a member, so the node's session and key pair survive
|
|
175
|
+
* a workload, readiness or policy change; the node binds a fresh answer with it to tell a successor,
|
|
176
|
+
* which keeps its live session, from a new membership, which replaces it. A previous credential for
|
|
177
|
+
* the same projection binds too, which is how a renewal moves the expiry. Content binding only, as
|
|
178
|
+
* for the response binder.
|
|
179
|
+
*/
|
|
180
|
+
export const bindGetRuntimeManagedVpnCredentialSuccessor = async (value: unknown, previousValue: unknown,
|
|
181
|
+
previousProjectionValue: unknown, projectionValue: unknown, trustedSessionValue: unknown, nowValue: unknown,
|
|
182
|
+
renewalValue: unknown | null = null): Promise<IGetRuntimeManagedVpnCredentialResponse> => {
|
|
183
|
+
try {
|
|
184
|
+
const response = snapshotGetRuntimeManagedVpnCredentialResponse(value);
|
|
185
|
+
const previous = snapshotGetRuntimeManagedVpnCredentialResponse(previousValue);
|
|
186
|
+
const previousInput = snapshotRuntimeNetworkProjection(previousProjectionValue);
|
|
187
|
+
const projectionInput = snapshotRuntimeNetworkProjection(projectionValue);
|
|
188
|
+
const trusted = snapshotRuntimeSessionBinding(trustedSessionValue), now = networkCounter(nowValue);
|
|
189
|
+
const renewalInput = renewalValue === null ? null : snapshotRuntimeNetworkDnsLeaseRenewal(renewalValue);
|
|
190
|
+
const previousProjection = await validateRuntimeNetworkProjection(previousInput);
|
|
191
|
+
const projection = await validateRuntimeNetworkProjection(projectionInput);
|
|
192
|
+
const renewal = renewalInput === null ? null : await validateRuntimeNetworkDnsLeaseRenewal(renewalInput);
|
|
193
|
+
requireNetwork(continuesMembership(previousProjection, projection));
|
|
194
|
+
requireNetwork(networkEqual(previous.session, trusted)
|
|
195
|
+
&& networkEqual(previous.projection, networkReferenceOf(previousProjection)));
|
|
196
|
+
requireNetwork(networkEqual(credentialIdentity(previous), credentialIdentity(response)));
|
|
197
|
+
return bindResponse(response, { session: trusted, projection: networkReferenceOf(projection) }, projection, renewal, trusted, now);
|
|
101
198
|
} catch { return failNetwork(); }
|
|
102
199
|
};
|