@serve.zone/interfaces 31.7.0 → 31.8.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 +32 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/index.d.ts +1 -0
- package/dist_ts/data/index.js +2 -1
- package/dist_ts/data/networkcredential.d.ts +10 -4
- package/dist_ts/data/networkcredential.js +14 -8
- package/dist_ts/data/networkdelivery.d.ts +21 -0
- package/dist_ts/data/networkdelivery.js +35 -7
- package/dist_ts/data/networklease.d.ts +110 -0
- package/dist_ts/data/networklease.js +159 -0
- package/dist_ts/data/networkprojection.d.ts +10 -5
- package/dist_ts/data/networkprojection.js +15 -22
- package/dist_ts/data/networksigning.js +5 -4
- package/dist_ts/private/networkcontract.d.ts +16 -0
- package/dist_ts/private/networkcontract.js +31 -1
- package/dist_ts/requests/cluster.d.ts +16 -0
- package/dist_ts/requests/cluster.js +1 -1
- package/dist_ts/requests/runtimesession.d.ts +14 -0
- package/dist_ts/runtime.networkprojection.d.ts +14 -0
- package/dist_ts/runtime.networkprojection.js +68 -19
- package/package.json +2 -2
- package/readme.md +131 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/index.ts +1 -0
- package/ts/data/networkcredential.ts +19 -9
- package/ts/data/networkdelivery.ts +49 -6
- package/ts/data/networklease.ts +229 -0
- package/ts/data/networkprojection.ts +28 -26
- package/ts/data/networksigning.ts +5 -4
- package/ts/private/networkcontract.ts +34 -0
- package/ts/requests/cluster.ts +20 -0
- package/ts/requests/runtimesession.ts +17 -0
- package/ts/runtime.networkprojection.ts +87 -20
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as plugins from './plugins.runtime.js';
|
|
2
2
|
import type { ISignedRuntimeNetworkProjectionV1 } from './data/networksigning.js';
|
|
3
|
+
import type { ISignedRuntimeNetworkDnsLeaseRenewal } from './data/networklease.js';
|
|
3
4
|
/** The private KeyObject is process-local. Persist only through the controller's encrypted secret store. */
|
|
4
5
|
export declare const createRuntimeNetworkSigningKeyV1: () => {
|
|
5
6
|
publicKey: string;
|
|
@@ -13,3 +14,16 @@ export declare const signRuntimeNetworkProjectionV1: (projectionValue: unknown,
|
|
|
13
14
|
* admission, live-session fencing, allocation barriers and conservative DNS time checks.
|
|
14
15
|
*/
|
|
15
16
|
export declare const verifyRuntimeNetworkProjectionV1: (value: unknown, trustedAuthorityValue: unknown, trustedScopeValue: unknown) => Promise<ISignedRuntimeNetworkProjectionV1>;
|
|
17
|
+
/** Sign only a validated renewal, with the same authority and key that sign this controller's projections. */
|
|
18
|
+
export declare const signRuntimeNetworkDnsLeaseRenewal: (renewalValue: unknown, authorityValue: unknown, privateKey: plugins.crypto.KeyObject) => Promise<ISignedRuntimeNetworkDnsLeaseRenewal>;
|
|
19
|
+
/**
|
|
20
|
+
* trustedAuthorityValue is receiver-owned persisted trust, NEVER envelope material, and a relay
|
|
21
|
+
* that delivers the renewal adds no trust to it. projectionAuthorityValue is the authority
|
|
22
|
+
* reference that signed the projection this renewal extends, which the receiver kept when it
|
|
23
|
+
* admitted that projection: a renewal is only ever valid under the exact revision that signed its
|
|
24
|
+
* projection, so a rotated key renews nothing and DNS still waits for a newly signed projection.
|
|
25
|
+
* This verifies content, scope and signature only: the owner still admits it against its own
|
|
26
|
+
* admitted projection with `admitRuntimeNetworkDnsLeaseRenewal`, fences the live session and
|
|
27
|
+
* converts the window with a qualified clock.
|
|
28
|
+
*/
|
|
29
|
+
export declare const verifyRuntimeNetworkDnsLeaseRenewal: (value: unknown, trustedAuthorityValue: unknown, trustedScopeValue: unknown, projectionAuthorityValue: unknown) => Promise<ISignedRuntimeNetworkDnsLeaseRenewal>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as plugins from './plugins.runtime.js';
|
|
2
2
|
import { snapshotRuntimeNetworkNodeScopeV1, snapshotRuntimeNetworkProjectionV1, validateRuntimeNetworkProjectionV1 } from './data/networkprojection.js';
|
|
3
3
|
import { computeRuntimeNetworkSigningAuthorityV1Digest, runtimeNetworkSigningV1Contract, snapshotRuntimeNetworkSigningAuthorityV1, snapshotSignedRuntimeNetworkProjectionV1 } from './data/networksigning.js';
|
|
4
|
-
import {
|
|
4
|
+
import { runtimeNetworkDnsLeaseRenewalContract, snapshotRuntimeNetworkDnsLeaseRenewal, snapshotSignedRuntimeNetworkDnsLeaseRenewal, validateRuntimeNetworkDnsLeaseRenewal } from './data/networklease.js';
|
|
5
|
+
import { captureNetwork, failNetwork, networkCanonical, networkEqual, networkReference, networkReferenceOf, networkSameScope, requireNetwork } from './private/networkcontract.js';
|
|
5
6
|
/** The private KeyObject is process-local. Persist only through the controller's encrypted secret store. */
|
|
6
7
|
export const createRuntimeNetworkSigningKeyV1 = () => {
|
|
7
8
|
const pair = plugins.crypto.generateKeyPairSync('ed25519');
|
|
@@ -9,23 +10,39 @@ export const createRuntimeNetworkSigningKeyV1 = () => {
|
|
|
9
10
|
requireNetwork(jwk.kty === 'OKP' && jwk.crv === 'Ed25519' && typeof jwk.x === 'string');
|
|
10
11
|
return { publicKey: Buffer.from(jwk.x, 'base64url').toString('hex'), privateKey: pair.privateKey };
|
|
11
12
|
};
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
13
|
+
const projectionEnvelope = (authority, projection) => ({ domain: runtimeNetworkSigningV1Contract.signatureDomain, schemaVersion: 1, authority, projection });
|
|
14
|
+
const renewalEnvelope = (authority, renewal) => ({ domain: runtimeNetworkDnsLeaseRenewalContract.signatureDomain, authority, renewal });
|
|
15
|
+
const envelopeBytes = (envelope) => new TextEncoder().encode(networkCanonical(envelope));
|
|
16
|
+
/** A revoked revision has no key; any other must be intact and belong to the controller that owns the content. */
|
|
17
|
+
const liveKeyOf = async (authority, contentController) => {
|
|
18
|
+
const { publicKey } = authority;
|
|
19
|
+
requireNetwork(publicKey !== null && networkEqual(authority.controller, contentController)
|
|
15
20
|
&& authority.digest === await computeRuntimeNetworkSigningAuthorityV1Digest(authority));
|
|
21
|
+
return publicKey;
|
|
22
|
+
};
|
|
23
|
+
/** Sign as the authority's revision, and only with the private half of its live key. */
|
|
24
|
+
const signAs = async (authority, privateKey, contentController, envelopeOf) => {
|
|
25
|
+
requireNetwork(privateKey instanceof plugins.crypto.KeyObject && privateKey.type === 'private' && privateKey.asymmetricKeyType === 'ed25519');
|
|
26
|
+
const derived = plugins.crypto.createPublicKey(privateKey).export({ format: 'jwk' });
|
|
27
|
+
const publicKey = await liveKeyOf(authority, contentController);
|
|
28
|
+
requireNetwork(typeof derived.x === 'string' && Buffer.from(derived.x, 'base64url').toString('hex') === publicKey);
|
|
29
|
+
const reference = networkReferenceOf(authority);
|
|
30
|
+
return { authority: reference, signature: plugins.crypto.sign(null, envelopeBytes(envelopeOf(reference)), privateKey).toString('hex') };
|
|
31
|
+
};
|
|
32
|
+
/** The receiver's own trusted revision decides; the envelope only names which revision it claims. */
|
|
33
|
+
const requireSignedBy = async (trusted, signed, contentController, envelope) => {
|
|
34
|
+
requireNetwork(networkEqual(signed.authority, networkReferenceOf(trusted)));
|
|
35
|
+
const key = plugins.crypto.createPublicKey({ format: 'jwk', key: { kty: 'OKP', crv: 'Ed25519',
|
|
36
|
+
x: Buffer.from(await liveKeyOf(trusted, contentController), 'hex').toString('base64url') } });
|
|
37
|
+
requireNetwork(plugins.crypto.verify(null, envelopeBytes(envelope), key, Buffer.from(signed.signature, 'hex')));
|
|
16
38
|
};
|
|
17
39
|
/** Sign only validated complete state and a matching dedicated private key. */
|
|
18
40
|
export const signRuntimeNetworkProjectionV1 = async (projectionValue, authorityValue, privateKey) => {
|
|
19
41
|
try {
|
|
20
42
|
const input = snapshotRuntimeNetworkProjectionV1(projectionValue), authority = snapshotRuntimeNetworkSigningAuthorityV1(authorityValue);
|
|
21
|
-
requireNetwork(privateKey instanceof plugins.crypto.KeyObject && privateKey.type === 'private' && privateKey.asymmetricKeyType === 'ed25519');
|
|
22
|
-
const publicKey = plugins.crypto.createPublicKey(privateKey).export({ format: 'jwk' });
|
|
23
|
-
requireNetwork(typeof publicKey.x === 'string' && Buffer.from(publicKey.x, 'base64url').toString('hex') === authority.publicKey);
|
|
24
43
|
const projection = await validateRuntimeNetworkProjectionV1(input);
|
|
25
|
-
await
|
|
26
|
-
|
|
27
|
-
const signature = plugins.crypto.sign(null, signatureBytes(reference, projection), privateKey).toString('hex');
|
|
28
|
-
return { schemaVersion: 1, authority: reference, projection, signature };
|
|
44
|
+
const signed = await signAs(authority, privateKey, projection.controller, reference => projectionEnvelope(reference, projection));
|
|
45
|
+
return { schemaVersion: 1, authority: signed.authority, projection, signature: signed.signature };
|
|
29
46
|
}
|
|
30
47
|
catch {
|
|
31
48
|
return failNetwork();
|
|
@@ -41,13 +58,8 @@ export const verifyRuntimeNetworkProjectionV1 = async (value, trustedAuthorityVa
|
|
|
41
58
|
const signed = snapshotSignedRuntimeNetworkProjectionV1(value);
|
|
42
59
|
const authority = snapshotRuntimeNetworkSigningAuthorityV1(trustedAuthorityValue);
|
|
43
60
|
const trusted = snapshotRuntimeNetworkNodeScopeV1(trustedScopeValue);
|
|
44
|
-
requireNetwork(
|
|
45
|
-
|
|
46
|
-
&& networkEqual(signed.projection.controller, trusted.controller));
|
|
47
|
-
await validateAuthority(authority, signed.projection);
|
|
48
|
-
const key = plugins.crypto.createPublicKey({ format: 'jwk', key: { kty: 'OKP', crv: 'Ed25519',
|
|
49
|
-
x: Buffer.from(authority.publicKey, 'hex').toString('base64url') } });
|
|
50
|
-
requireNetwork(plugins.crypto.verify(null, signatureBytes(signed.authority, signed.projection), key, Buffer.from(signed.signature, 'hex')));
|
|
61
|
+
requireNetwork(networkSameScope(signed.projection, trusted));
|
|
62
|
+
await requireSignedBy(authority, signed, signed.projection.controller, projectionEnvelope(signed.authority, signed.projection));
|
|
51
63
|
await validateRuntimeNetworkProjectionV1(signed.projection);
|
|
52
64
|
return signed;
|
|
53
65
|
}
|
|
@@ -55,4 +67,41 @@ export const verifyRuntimeNetworkProjectionV1 = async (value, trustedAuthorityVa
|
|
|
55
67
|
return failNetwork();
|
|
56
68
|
}
|
|
57
69
|
};
|
|
58
|
-
|
|
70
|
+
/** Sign only a validated renewal, with the same authority and key that sign this controller's projections. */
|
|
71
|
+
export const signRuntimeNetworkDnsLeaseRenewal = async (renewalValue, authorityValue, privateKey) => {
|
|
72
|
+
try {
|
|
73
|
+
const input = snapshotRuntimeNetworkDnsLeaseRenewal(renewalValue), authority = snapshotRuntimeNetworkSigningAuthorityV1(authorityValue);
|
|
74
|
+
const renewal = await validateRuntimeNetworkDnsLeaseRenewal(input);
|
|
75
|
+
const signed = await signAs(authority, privateKey, renewal.controller, reference => renewalEnvelope(reference, renewal));
|
|
76
|
+
return { authority: signed.authority, renewal, signature: signed.signature };
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return failNetwork();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* trustedAuthorityValue is receiver-owned persisted trust, NEVER envelope material, and a relay
|
|
84
|
+
* that delivers the renewal adds no trust to it. projectionAuthorityValue is the authority
|
|
85
|
+
* reference that signed the projection this renewal extends, which the receiver kept when it
|
|
86
|
+
* admitted that projection: a renewal is only ever valid under the exact revision that signed its
|
|
87
|
+
* projection, so a rotated key renews nothing and DNS still waits for a newly signed projection.
|
|
88
|
+
* This verifies content, scope and signature only: the owner still admits it against its own
|
|
89
|
+
* admitted projection with `admitRuntimeNetworkDnsLeaseRenewal`, fences the live session and
|
|
90
|
+
* converts the window with a qualified clock.
|
|
91
|
+
*/
|
|
92
|
+
export const verifyRuntimeNetworkDnsLeaseRenewal = async (value, trustedAuthorityValue, trustedScopeValue, projectionAuthorityValue) => {
|
|
93
|
+
try {
|
|
94
|
+
const signed = snapshotSignedRuntimeNetworkDnsLeaseRenewal(value);
|
|
95
|
+
const authority = snapshotRuntimeNetworkSigningAuthorityV1(trustedAuthorityValue);
|
|
96
|
+
const trusted = snapshotRuntimeNetworkNodeScopeV1(trustedScopeValue);
|
|
97
|
+
const projectionAuthority = networkReference(captureNetwork(projectionAuthorityValue));
|
|
98
|
+
requireNetwork(networkSameScope(signed.renewal, trusted) && networkEqual(signed.authority, projectionAuthority));
|
|
99
|
+
await requireSignedBy(authority, signed, signed.renewal.controller, renewalEnvelope(signed.authority, signed.renewal));
|
|
100
|
+
await validateRuntimeNetworkDnsLeaseRenewal(signed.renewal);
|
|
101
|
+
return signed;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return failNetwork();
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVudGltZS5uZXR3b3JrcHJvamVjdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3J1bnRpbWUubmV0d29ya3Byb2plY3Rpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxzQkFBc0IsQ0FBQztBQUdoRCxPQUFPLEVBQUUsaUNBQWlDLEVBQUUsa0NBQWtDLEVBQUUsa0NBQWtDLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUV4SixPQUFPLEVBQUUsNkNBQTZDLEVBQUUsK0JBQStCLEVBQ3JGLHdDQUF3QyxFQUFFLHdDQUF3QyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFdkgsT0FBTyxFQUFFLHFDQUFxQyxFQUFFLHFDQUFxQyxFQUNuRiwyQ0FBMkMsRUFBRSxxQ0FBcUMsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ3JILE9BQU8sRUFBRSxjQUFjLEVBQUUsV0FBVyxFQUFFLGdCQUFnQixFQUFFLFlBQVksRUFBRSxnQkFBZ0IsRUFBRSxrQkFBa0IsRUFDeEcsZ0JBQWdCLEVBQUUsY0FBYyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFFekUsNEdBQTRHO0FBQzVHLE1BQU0sQ0FBQyxNQUFNLGdDQUFnQyxHQUFHLEdBQWdFLEVBQUU7SUFDaEgsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxtQkFBbUIsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUMzRCxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBQ3JELGNBQWMsQ0FBQyxHQUFHLENBQUMsR0FBRyxLQUFLLEtBQUssSUFBSSxHQUFHLENBQUMsR0FBRyxLQUFLLFNBQVMsSUFBSSxPQUFPLEdBQUcsQ0FBQyxDQUFDLEtBQUssUUFBUSxDQUFDLENBQUM7SUFDeEYsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLENBQUMsSUFBSSxD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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serve.zone/interfaces",
|
|
3
|
-
"version": "31.
|
|
3
|
+
"version": "31.8.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
|
|
6
6
|
"exports": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@git.zone/tsdoc": "^2.2.0",
|
|
32
32
|
"@git.zone/tsrun": "^2.0.6",
|
|
33
33
|
"@git.zone/tstest": "6.1.1",
|
|
34
|
-
"@types/node": "26.5.
|
|
34
|
+
"@types/node": "26.5.1"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"ts/**/*",
|
package/readme.md
CHANGED
|
@@ -371,7 +371,8 @@ set. An empty array declares NODATA; absent/unauthorized names remain NXDOMAIN,
|
|
|
371
371
|
and IPv4-only service names have AAAA NODATA. The view carries the existing
|
|
372
372
|
forwarding intersection and zero or one search suffix. Pallet must prove the
|
|
373
373
|
actual sandbox/veth/source binding, block private/bare-name forwarding, clip
|
|
374
|
-
TTLs and convert the
|
|
374
|
+
TTLs and convert the effective window from `getRuntimeNetworkEffectiveDnsWindow` (see
|
|
375
|
+
[DNS Lease Renewals](#dns-lease-renewals)) using its qualified boot/time authority.
|
|
375
376
|
These helpers neither cache queries nor renew a lease.
|
|
376
377
|
|
|
377
378
|
The DNS composer accepts an optional second argument of exact local workload
|
|
@@ -454,10 +455,11 @@ capture closed detached JSON shapes and canonical 32-byte key encodings.
|
|
|
454
455
|
`bindGetRuntimeManagedVpnCredentialRequestV1(request, projection, trustedSession)`
|
|
455
456
|
validates the complete projection digest and binds node, controller, namespace,
|
|
456
457
|
physical-session identity and projection reference. The response binder takes
|
|
457
|
-
`(response, request, projection, trustedSession, now)` and additionally requires
|
|
458
|
+
`(response, request, projection, trustedSession, now, renewal = null)` and additionally requires
|
|
458
459
|
the relay's exact UDP tuple to be selected in `router.platformEndpointIds`.
|
|
459
460
|
Expiry must be after the caller-qualified time and no later than the referenced
|
|
460
|
-
projection's DNS
|
|
461
|
+
projection's effective DNS window (see [DNS Lease Renewals](#dns-lease-renewals));
|
|
462
|
+
acceptance before that window starts rejects.
|
|
461
463
|
|
|
462
464
|
These helpers perform content validation. Callers authenticate and fence the
|
|
463
465
|
current physical peer and projection before and after native work, qualify time
|
|
@@ -466,6 +468,132 @@ Noise implementation owns cryptographic validation and authentication. Credentia
|
|
|
466
468
|
delivery does not prove a connected VPN, TUN ownership, packet enforcement or
|
|
467
469
|
workload readiness, and does not replace explicit packet withdrawals.
|
|
468
470
|
|
|
471
|
+
### DNS Lease Renewals
|
|
472
|
+
|
|
473
|
+
A projection's `dns` window lasts at most fifteen minutes and is signed content, so a node's
|
|
474
|
+
private DNS stops when the window ends unless a newer signed statement extends it. Re-issuing the
|
|
475
|
+
projection would extend it too, but a node applies every new projection as new network state,
|
|
476
|
+
which restarts its data plane. `IRuntimeNetworkDnsLeaseRenewal` extends the window and nothing
|
|
477
|
+
else: it carries the node scope (`controller`, `nodeId`, `runtimeNamespace`), the exact
|
|
478
|
+
`projection` reference it renews, a `sequence`, a fresh `dns { issuedAt, notBefore, expiresAt }`
|
|
479
|
+
window and its `digest`. It names the projection by reference, so it never joins the projection
|
|
480
|
+
chain and never changes what the node applied. The new shapes carry no `schemaVersion` and no
|
|
481
|
+
version suffix; the installed package version is the protocol version.
|
|
482
|
+
|
|
483
|
+
`issuedAt` is the start of the window's slot, not the moment of signing: Cloudly signs renewals
|
|
484
|
+
ahead of time on a grid, so the fifteen-minute rule (`maximumDnsLeaseMs`, the same limit a
|
|
485
|
+
projection's window has) is measured from the slot. `computeRuntimeNetworkDnsLeaseRenewalDigest`
|
|
486
|
+
hashes every field except `digest` under its own domain, and `validateRuntimeNetworkDnsLeaseRenewal`
|
|
487
|
+
checks structure and digest only; a digest authenticates nothing.
|
|
488
|
+
|
|
489
|
+
The Node-only `@serve.zone/interfaces/runtime` export signs and verifies renewals with the same
|
|
490
|
+
authority and key that sign the controller's projections, under a separate signature domain.
|
|
491
|
+
`signRuntimeNetworkDnsLeaseRenewal(renewal, authority, privateKey)` produces
|
|
492
|
+
`ISignedRuntimeNetworkDnsLeaseRenewal { authority, renewal, signature }`.
|
|
493
|
+
`verifyRuntimeNetworkDnsLeaseRenewal(signed, trustedAuthority, trustedScope, projectionAuthority)`
|
|
494
|
+
takes the authority and scope from the node's own stored trust, never from the envelope.
|
|
495
|
+
`projectionAuthority` is the reference of the authority revision that signed the projection the
|
|
496
|
+
node admitted, and a renewal verifies only when it was signed under exactly that revision.
|
|
497
|
+
Rotation and revocation end renewals exactly as they end projections: nothing signed under an
|
|
498
|
+
earlier revision verifies after, and a renewal signed under a rotated revision does not renew a
|
|
499
|
+
projection signed under the one before. A renewal never closes a key gap; after rotation, DNS
|
|
500
|
+
continues only with a newly signed projection.
|
|
501
|
+
|
|
502
|
+
`admitRuntimeNetworkDnsLeaseRenewal(renewal, currentProjection, previousRenewal | null, trustedScope, now)`
|
|
503
|
+
is the pure admission decision after verification. The projection and the previous renewal are
|
|
504
|
+
the node's own admitted state, and `now` is its caller-qualified clock, read before the first
|
|
505
|
+
await like every other input. It admits a renewal only when:
|
|
506
|
+
|
|
507
|
+
- the renewal and the projection are in the trusted scope, and the renewal names the current
|
|
508
|
+
projection's exact reference. Once a successor projection is admitted, every renewal of its
|
|
509
|
+
predecessor is refused, and a renewal never introduces a projection the node has not admitted;
|
|
510
|
+
- its `sequence` is higher than the previous renewal's, with gaps allowed, or equal with
|
|
511
|
+
byte-identical content, which is answered as `replay` whatever `now` is, so re-delivery is
|
|
512
|
+
idempotent. The same sequence with any other content is refused;
|
|
513
|
+
- its window is current: `notBefore <= now < expiresAt`. Admitting a window that has not opened
|
|
514
|
+
would displace the coverage the node has now, so a sender delivers a window only once its slot
|
|
515
|
+
has opened; a renewal refused for arriving early is simply delivered again once it has. An
|
|
516
|
+
expired window is refused as well;
|
|
517
|
+
- its window does not move `issuedAt` back and ends later than the previous window. Without a
|
|
518
|
+
previous renewal, the projection's own window is the baseline. Windows need not touch, so a node
|
|
519
|
+
whose lease lapsed can still admit the next renewal it is given.
|
|
520
|
+
|
|
521
|
+
The caller stores the admitted renewal in the same transaction as its trust fence, and deletes it
|
|
522
|
+
in the transaction that admits a successor projection. Admission is not conversion: the node still
|
|
523
|
+
turns the window into a deadline with its qualified clock, and must not use a window before its
|
|
524
|
+
`notBefore`.
|
|
525
|
+
|
|
526
|
+
`requests.runtimesession.IReq_Controller_Pallet_ApplyRuntimeNetworkDnsLeaseRenewal`
|
|
527
|
+
(`applyRuntimeNetworkDnsLeaseRenewal`) delivers one renewal as
|
|
528
|
+
`IApplyRuntimeNetworkDnsLeaseRenewalRequest { session, signed }`.
|
|
529
|
+
`bindApplyRuntimeNetworkDnsLeaseRenewalRequest(request, trustedSession)` requires the node's
|
|
530
|
+
current physical-session binding and a renewal in that session's scope. It does not care who sent
|
|
531
|
+
the request: a cluster relay pushes under the binding the node obtained itself. The answer,
|
|
532
|
+
`IApplyRuntimeNetworkDnsLeaseRenewalResponse { status, renewal }`, repeats
|
|
533
|
+
`IRuntimeNetworkDnsLeaseRenewalReference { projection, sequence, digest }`.
|
|
534
|
+
`bindApplyRuntimeNetworkDnsLeaseRenewalResponse(response, sentRenewal)` compares it with the exact
|
|
535
|
+
renewal that was sent. The ACK asserts durable admission only, not that the node serves DNS.
|
|
536
|
+
|
|
537
|
+
A cluster is outbound-only, so while its relay cannot reach Cloudly nobody can sign a new window.
|
|
538
|
+
Cloudly therefore hands the elected relay a run of renewals it signed ahead of time, with
|
|
539
|
+
`requests.cluster.IReq_Cloudly_Relay_HoldRuntimeNetworkDnsLeaseRenewals`
|
|
540
|
+
(`holdRuntimeNetworkDnsLeaseRenewals`):
|
|
541
|
+
`IHoldRuntimeNetworkDnsLeaseRenewalsRequest { nodeId, projection, renewals }` answered by
|
|
542
|
+
`{ held }`. Each request replaces what the relay held for that node, and an empty run holds
|
|
543
|
+
nothing. The relay keeps the run in memory only, pushes only the renewal whose window is current
|
|
544
|
+
and only while it has no Cloudly session (`escrowRelease`), and holds no key and no trust.
|
|
545
|
+
`snapshotHoldRuntimeNetworkDnsLeaseRenewalsRequest` checks the rules a relay can evaluate without
|
|
546
|
+
the projection itself; the node still measures the first window against its admitted projection,
|
|
547
|
+
and every window against its clock, when it admits it. The run must have:
|
|
548
|
+
|
|
549
|
+
- one node, one projection and one signing authority revision, in one scope;
|
|
550
|
+
- strictly ascending sequences, each window extending the one before;
|
|
551
|
+
- at most `maximumEscrowedRenewals` (32) renewals;
|
|
552
|
+
- at most `maximumEscrowHorizonMs` (four hours) from the first window's `issuedAt` to the last
|
|
553
|
+
window's `expiresAt`. On the intended grid of fifteen-minute windows every ten minutes, the
|
|
554
|
+
horizon allows 23 windows, so it is the limit that binds.
|
|
555
|
+
|
|
556
|
+
`validateHoldRuntimeNetworkDnsLeaseRenewalsRequest` adds every renewal's digest check and
|
|
557
|
+
verifies no signature. `bindHoldRuntimeNetworkDnsLeaseRenewalsResponse(response, sentRequest)`
|
|
558
|
+
requires `held` to equal the number of renewals sent.
|
|
559
|
+
|
|
560
|
+
The horizon is also the revocation latency: a detached cluster keeps DNS authority until its last
|
|
561
|
+
held window ends. So a controller must not release an address or a DNS name before the latest
|
|
562
|
+
`expiresAt` it has signed for that node. Renewals extend DNS only. They grant no packet authority
|
|
563
|
+
and cannot introduce new network state, and they do not extend an already-issued VPN credential;
|
|
564
|
+
a newly issued one may be bound to the renewed window.
|
|
565
|
+
|
|
566
|
+
`getRuntimeNetworkEffectiveDnsWindow(projection, renewal | null)` is the one rule for the window a
|
|
567
|
+
node may use: the admitted renewal's if there is one, else the projection's own. `renewal` is the
|
|
568
|
+
bare `IRuntimeNetworkDnsLeaseRenewal` that admission takes and returns, which is what a node
|
|
569
|
+
stores; Cloudly passes `signed.renewal`. It checks digests and that the renewal names exactly that
|
|
570
|
+
projection, but verifies no signature. Controller and node use it for every limit tied to the
|
|
571
|
+
window, and `bindGetRuntimeManagedVpnCredentialResponseV1` takes the same bare admitted renewal as
|
|
572
|
+
an optional last argument, so a newly issued managed-VPN credential may run to the end of the
|
|
573
|
+
renewed window instead of the projection's original one.
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
import { data } from '@serve.zone/interfaces';
|
|
577
|
+
import { signRuntimeNetworkDnsLeaseRenewal, verifyRuntimeNetworkDnsLeaseRenewal } from '@serve.zone/interfaces/runtime';
|
|
578
|
+
|
|
579
|
+
const renewal: data.IRuntimeNetworkDnsLeaseRenewal = {
|
|
580
|
+
...scope,
|
|
581
|
+
projection: { id: projection.id, generation: projection.generation, digest: projection.digest },
|
|
582
|
+
sequence: 1,
|
|
583
|
+
dns: { issuedAt: slotStart, notBefore: slotStart, expiresAt: slotStart + 900_000 },
|
|
584
|
+
digest: `sha256:${'0'.repeat(64)}` as data.TSha256Digest, // well-formed placeholder, replaced below
|
|
585
|
+
};
|
|
586
|
+
renewal.digest = await data.computeRuntimeNetworkDnsLeaseRenewalDigest(renewal);
|
|
587
|
+
const signed = await signRuntimeNetworkDnsLeaseRenewal(renewal, signingAuthority, privateKey);
|
|
588
|
+
|
|
589
|
+
// On the node: stored trust, then the node's own admitted state and qualified clock.
|
|
590
|
+
const verified = await verifyRuntimeNetworkDnsLeaseRenewal(
|
|
591
|
+
signed, storedAuthority, nodeScope, admittedProjectionAuthority);
|
|
592
|
+
const { renewal: admittedRenewal } = await data.admitRuntimeNetworkDnsLeaseRenewal(
|
|
593
|
+
verified.renewal, admittedProjection, previousRenewal, nodeScope, qualifiedNow);
|
|
594
|
+
const window = await data.getRuntimeNetworkEffectiveDnsWindow(admittedProjection, admittedRenewal);
|
|
595
|
+
```
|
|
596
|
+
|
|
469
597
|
### Node-Bound Runtime Assignments
|
|
470
598
|
|
|
471
599
|
The `data` namespace exports `IRuntimeAssignmentV1`, canonical digest helpers,
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/data/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export * from './nodeenrollment.js';
|
|
|
27
27
|
export * from './networkauthority.js';
|
|
28
28
|
export * from './networkcredential.js';
|
|
29
29
|
export * from './networkdelivery.js';
|
|
30
|
+
export * from './networklease.js';
|
|
30
31
|
export * from './networkprojection.js';
|
|
31
32
|
export type { IRuntimeNetworkRouterPacketGrantV1, IRuntimeNetworkRouterProjectionV1 } from './networkrouter.js';
|
|
32
33
|
export * from './networkprotection.js';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { IRuntimeAuthorityReferenceV1 } from './runtimeassignment.js';
|
|
2
2
|
import { snapshotRuntimeSessionBindingV1, type IRuntimeSessionBindingV1 } from './runtimesession.js';
|
|
3
3
|
import { validateRuntimeNetworkProjectionV1, type IRuntimeNetworkProjectionV1 } from './networkprojection.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import { snapshotRuntimeNetworkDnsLeaseRenewal, validateRuntimeNetworkDnsLeaseRenewal } from './networklease.js';
|
|
5
|
+
import { captureNetwork, failNetwork, networkCounter, networkEffectiveDnsWindow, networkEqual, networkId, networkIpv4,
|
|
6
|
+
networkRecord, networkReference, networkReferenceOf, networkSafe, networkSameScope, requireNetwork } from '../private/networkcontract.js';
|
|
6
7
|
|
|
7
8
|
export interface IGetRuntimeManagedVpnCredentialRequestV1 {
|
|
8
9
|
session: IRuntimeSessionBindingV1;
|
|
@@ -19,7 +20,10 @@ export interface IGetRuntimeManagedVpnCredentialResponseV1 extends IGetRuntimeMa
|
|
|
19
20
|
serverPublicKey: string;
|
|
20
21
|
clientPublicKey: string;
|
|
21
22
|
clientPrivateKey: string;
|
|
22
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Unix milliseconds; cannot outlive the referenced projection's effective DNS window: its
|
|
25
|
+
* admitted lease renewal's, else its own.
|
|
26
|
+
*/
|
|
23
27
|
expiresAt: number;
|
|
24
28
|
}
|
|
25
29
|
|
|
@@ -50,8 +54,7 @@ export const snapshotGetRuntimeManagedVpnCredentialResponseV1 = (value: unknown)
|
|
|
50
54
|
const bindRequest = (request: IGetRuntimeManagedVpnCredentialRequestV1,
|
|
51
55
|
projection: IRuntimeNetworkProjectionV1, trusted: IRuntimeSessionBindingV1): void => {
|
|
52
56
|
requireNetwork(networkEqual(request.session, trusted) && networkEqual(request.projection, networkReferenceOf(projection))
|
|
53
|
-
&&
|
|
54
|
-
&& projection.runtimeNamespace === trusted.runtimeNamespace && projection.handoff);
|
|
57
|
+
&& networkSameScope(projection, trusted) && projection.handoff);
|
|
55
58
|
};
|
|
56
59
|
|
|
57
60
|
/** Content binding only. The caller must resolve the exact current projection
|
|
@@ -68,21 +71,28 @@ export const bindGetRuntimeManagedVpnCredentialRequestV1 = async (value: unknown
|
|
|
68
71
|
};
|
|
69
72
|
|
|
70
73
|
/** Bind the secret response to the original request and exact selected router
|
|
71
|
-
* UDP endpoint
|
|
72
|
-
*
|
|
74
|
+
* UDP endpoint, inside the projection's effective DNS window. renewalValue is the
|
|
75
|
+
* bare lease renewal the caller already verified and admitted for this exact
|
|
76
|
+
* projection, or null; this helper verifies no signature. Time is caller-qualified;
|
|
77
|
+
* this helper does not sample a clock, authenticate a transport, admit policy or
|
|
78
|
+
* prove a running managed connection. */
|
|
73
79
|
export const bindGetRuntimeManagedVpnCredentialResponseV1 = async (value: unknown, requestValue: unknown,
|
|
74
|
-
projectionValue: unknown, trustedSessionValue: unknown, nowValue: unknown
|
|
80
|
+
projectionValue: unknown, trustedSessionValue: unknown, nowValue: unknown,
|
|
81
|
+
renewalValue: unknown | null = null): Promise<IGetRuntimeManagedVpnCredentialResponseV1> => {
|
|
75
82
|
try {
|
|
76
83
|
const response = snapshotGetRuntimeManagedVpnCredentialResponseV1(value);
|
|
77
84
|
const request = snapshotGetRuntimeManagedVpnCredentialRequestV1(requestValue);
|
|
78
85
|
const trusted = snapshotRuntimeSessionBindingV1(trustedSessionValue), now = networkCounter(nowValue);
|
|
86
|
+
const renewalInput = renewalValue === null ? null : snapshotRuntimeNetworkDnsLeaseRenewal(renewalValue);
|
|
79
87
|
const projection = await validateRuntimeNetworkProjectionV1(projectionValue);
|
|
88
|
+
const renewal = renewalInput === null ? null : await validateRuntimeNetworkDnsLeaseRenewal(renewalInput);
|
|
89
|
+
const window = networkEffectiveDnsWindow(projection, renewal);
|
|
80
90
|
bindRequest(request, projection, trusted);
|
|
81
91
|
requireNetwork(networkEqual(response.session, request.session) && networkEqual(response.projection, request.projection));
|
|
82
92
|
requireNetwork(projection.router?.platformEndpointIds.includes(response.relay.id));
|
|
83
93
|
const endpoint = projection.protectedAuthority.platformEndpoints.find(item => item.id === response.relay.id);
|
|
84
94
|
requireNetwork(endpoint && endpoint.protocol === 'udp' && endpoint.address === response.relay.address && endpoint.port === response.relay.port);
|
|
85
|
-
requireNetwork(
|
|
95
|
+
requireNetwork(window.notBefore <= now && now < response.expiresAt && response.expiresAt <= window.expiresAt);
|
|
86
96
|
return response;
|
|
87
97
|
} catch { return failNetwork(); }
|
|
88
98
|
};
|
|
@@ -3,7 +3,11 @@ import type { IRuntimeSessionBindingV1 } from './runtimesession.js';
|
|
|
3
3
|
import { snapshotRuntimeSessionBindingV1 } from './runtimesession.js';
|
|
4
4
|
import type { IRuntimeNetworkSigningAuthorityV1, ISignedRuntimeNetworkProjectionV1 } from './networksigning.js';
|
|
5
5
|
import { snapshotRuntimeNetworkSigningAuthorityV1, snapshotSignedRuntimeNetworkProjectionV1 } from './networksigning.js';
|
|
6
|
-
import {
|
|
6
|
+
import type { IRuntimeNetworkDnsLeaseRenewalReference, ISignedRuntimeNetworkDnsLeaseRenewal } from './networklease.js';
|
|
7
|
+
import { getRuntimeNetworkDnsLeaseRenewalReference, snapshotRuntimeNetworkDnsLeaseRenewal,
|
|
8
|
+
snapshotRuntimeNetworkDnsLeaseRenewalReference, snapshotSignedRuntimeNetworkDnsLeaseRenewal } from './networklease.js';
|
|
9
|
+
import { captureNetwork, networkEqual, networkRecord, networkReference, networkReferenceOf, networkSafe, networkSameScope,
|
|
10
|
+
requireNetwork } from '../private/networkcontract.js';
|
|
7
11
|
|
|
8
12
|
export interface IApplyRuntimeNetworkSigningAuthorityRequestV1 {
|
|
9
13
|
session: IRuntimeSessionBindingV1;
|
|
@@ -18,6 +22,19 @@ export interface IApplyRuntimeNetworkAuthorityResponseV1 {
|
|
|
18
22
|
status: 'accepted' | 'replay';
|
|
19
23
|
authority: IRuntimeAuthorityReferenceV1;
|
|
20
24
|
}
|
|
25
|
+
export interface IApplyRuntimeNetworkDnsLeaseRenewalRequest {
|
|
26
|
+
session: IRuntimeSessionBindingV1;
|
|
27
|
+
signed: ISignedRuntimeNetworkDnsLeaseRenewal;
|
|
28
|
+
}
|
|
29
|
+
/** Durable admission only. It does not assert that the node converted the window or serves DNS. */
|
|
30
|
+
export interface IApplyRuntimeNetworkDnsLeaseRenewalResponse {
|
|
31
|
+
status: 'accepted' | 'replay';
|
|
32
|
+
renewal: IRuntimeNetworkDnsLeaseRenewalReference;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const admissionStatus = (value: unknown): 'accepted' | 'replay' => {
|
|
36
|
+
requireNetwork(value === 'accepted' || value === 'replay'); return value;
|
|
37
|
+
};
|
|
21
38
|
|
|
22
39
|
export const snapshotApplyRuntimeNetworkSigningAuthorityRequestV1 = (value: unknown): IApplyRuntimeNetworkSigningAuthorityRequestV1 => networkSafe(() => {
|
|
23
40
|
const item = networkRecord(captureNetwork(value), ['session', 'authority']);
|
|
@@ -29,8 +46,16 @@ export const snapshotApplyRuntimeNetworkProjectionRequestV1 = (value: unknown):
|
|
|
29
46
|
});
|
|
30
47
|
export const snapshotApplyRuntimeNetworkAuthorityResponseV1 = (value: unknown): IApplyRuntimeNetworkAuthorityResponseV1 => networkSafe(() => {
|
|
31
48
|
const item = networkRecord(captureNetwork(value), ['status', 'authority']);
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
return { status: admissionStatus(item.status), authority: networkReference(item.authority) };
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const snapshotApplyRuntimeNetworkDnsLeaseRenewalRequest = (value: unknown): IApplyRuntimeNetworkDnsLeaseRenewalRequest => networkSafe(() => {
|
|
53
|
+
const item = networkRecord(captureNetwork(value), ['session', 'signed']);
|
|
54
|
+
return { session: snapshotRuntimeSessionBindingV1(item.session), signed: snapshotSignedRuntimeNetworkDnsLeaseRenewal(item.signed) };
|
|
55
|
+
});
|
|
56
|
+
export const snapshotApplyRuntimeNetworkDnsLeaseRenewalResponse = (value: unknown): IApplyRuntimeNetworkDnsLeaseRenewalResponse => networkSafe(() => {
|
|
57
|
+
const item = networkRecord(captureNetwork(value), ['status', 'renewal']);
|
|
58
|
+
return { status: admissionStatus(item.status), renewal: snapshotRuntimeNetworkDnsLeaseRenewalReference(item.renewal) };
|
|
34
59
|
});
|
|
35
60
|
|
|
36
61
|
/** trustedSessionValue must come from the receiving CURRENT physical connection. */
|
|
@@ -43,9 +68,7 @@ export const bindApplyRuntimeNetworkSigningAuthorityRequestV1 = (value: unknown,
|
|
|
43
68
|
/** Content binding only. Verify signature with stored trust and atomically fence admission. */
|
|
44
69
|
export const bindApplyRuntimeNetworkProjectionRequestV1 = (value: unknown, trustedSessionValue: unknown): IApplyRuntimeNetworkProjectionRequestV1 => networkSafe(() => {
|
|
45
70
|
const request = snapshotApplyRuntimeNetworkProjectionRequestV1(value), session = snapshotRuntimeSessionBindingV1(trustedSessionValue);
|
|
46
|
-
|
|
47
|
-
requireNetwork(networkEqual(request.session, session) && networkEqual(projection.controller, session.controller)
|
|
48
|
-
&& projection.nodeId === session.nodeId && projection.runtimeNamespace === session.runtimeNamespace);
|
|
71
|
+
requireNetwork(networkEqual(request.session, session) && networkSameScope(request.signed.projection, session));
|
|
49
72
|
return request;
|
|
50
73
|
});
|
|
51
74
|
|
|
@@ -56,3 +79,23 @@ export const bindApplyRuntimeNetworkAuthorityResponseV1 = (value: unknown, sentR
|
|
|
56
79
|
requireNetwork(networkEqual(response.authority, networkReferenceOf(expected)));
|
|
57
80
|
return response;
|
|
58
81
|
});
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Content binding only; trustedSessionValue must come from the receiving CURRENT physical
|
|
85
|
+
* connection. A relay pushes a held renewal under the binding the node obtained itself, so this
|
|
86
|
+
* comparison is what makes the push admissible, never who sent it. Verify the signature with
|
|
87
|
+
* stored trust, then admit and fence atomically.
|
|
88
|
+
*/
|
|
89
|
+
export const bindApplyRuntimeNetworkDnsLeaseRenewalRequest = (value: unknown, trustedSessionValue: unknown): IApplyRuntimeNetworkDnsLeaseRenewalRequest => networkSafe(() => {
|
|
90
|
+
const request = snapshotApplyRuntimeNetworkDnsLeaseRenewalRequest(value), session = snapshotRuntimeSessionBindingV1(trustedSessionValue);
|
|
91
|
+
requireNetwork(networkEqual(request.session, session) && networkSameScope(request.signed.renewal, session));
|
|
92
|
+
return request;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/** Compare the ACK with the exact SENT renewal, never with a response-derived value. */
|
|
96
|
+
export const bindApplyRuntimeNetworkDnsLeaseRenewalResponse = (value: unknown, sentRenewalValue: unknown): IApplyRuntimeNetworkDnsLeaseRenewalResponse => networkSafe(() => {
|
|
97
|
+
const response = snapshotApplyRuntimeNetworkDnsLeaseRenewalResponse(value);
|
|
98
|
+
const sent = getRuntimeNetworkDnsLeaseRenewalReference(snapshotRuntimeNetworkDnsLeaseRenewal(sentRenewalValue));
|
|
99
|
+
requireNetwork(networkEqual(response.renewal, sent));
|
|
100
|
+
return response;
|
|
101
|
+
});
|