@serve.zone/interfaces 30.1.0 → 30.3.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 +23 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/index.d.ts +2 -0
- package/dist_ts/data/index.js +3 -1
- package/dist_ts/data/privatenetwork.d.ts +96 -0
- package/dist_ts/data/privatenetwork.js +168 -0
- package/dist_ts/data/servicecredential.d.ts +57 -0
- package/dist_ts/data/servicecredential.js +81 -0
- package/dist_ts/requests/admin.d.ts +25 -0
- package/dist_ts/requests/network.d.ts +62 -0
- package/package.json +2 -2
- package/readme.md +44 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/index.ts +2 -0
- package/ts/data/privatenetwork.ts +234 -0
- package/ts/data/servicecredential.ts +130 -0
- package/ts/requests/admin.ts +25 -0
- package/ts/requests/network.ts +48 -0
package/changelog.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026-09-09 - 30.3.0
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- bump @git.zone/tstest to ^4.1.1 (deps-dev)
|
|
8
|
+
- Updated the test runner dev dependency from ^4.0.0 to ^4.1.1.
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
- add private network contracts and typed requests (private-networks)
|
|
13
|
+
- Introduce private network data models, snapshot validators, contract limits, and FQDN construction.
|
|
14
|
+
- Add Cloudly typed requests for listing, reading, mutating, and attaching service private networks.
|
|
15
|
+
- Document private network DNS, membership, retirement, and revocation semantics.
|
|
16
|
+
|
|
17
|
+
## 2026-09-09 - 30.2.0
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
- add service machine credential contracts (service-credentials)
|
|
22
|
+
- Add service machine credential grant, metadata and mutation contracts with structural snapshot validators.
|
|
23
|
+
- Expose admin typed requests for inspecting metadata and creating, rotating or revoking service machine credentials.
|
|
24
|
+
- Document SecretSet delivery semantics and add validation coverage for credential contract boundaries.
|
|
25
|
+
|
|
3
26
|
## 2026-09-09 - 30.1.0
|
|
4
27
|
|
|
5
28
|
### Features
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@serve.zone/interfaces',
|
|
6
|
-
version: '30.
|
|
6
|
+
version: '30.3.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=
|
package/dist_ts/data/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from './isolatedrestore.golden.js';
|
|
|
22
22
|
export * from './mail.js';
|
|
23
23
|
export * from './nodecredential.js';
|
|
24
24
|
export * from './nodeenrollment.js';
|
|
25
|
+
export * from './privatenetwork.js';
|
|
25
26
|
export * from './registry.js';
|
|
26
27
|
export * from './runtimeassignment.js';
|
|
27
28
|
export * from './runtimeconfig.js';
|
|
@@ -34,6 +35,7 @@ export * from './backup.js';
|
|
|
34
35
|
export * from './clusternode.js';
|
|
35
36
|
export * from './settings.js';
|
|
36
37
|
export * from './service.js';
|
|
38
|
+
export * from './servicecredential.js';
|
|
37
39
|
export * from './serviceports.js';
|
|
38
40
|
export * from './status.js';
|
|
39
41
|
export * from './taskexecution.js';
|
package/dist_ts/data/index.js
CHANGED
|
@@ -22,6 +22,7 @@ export * from './isolatedrestore.golden.js';
|
|
|
22
22
|
export * from './mail.js';
|
|
23
23
|
export * from './nodecredential.js';
|
|
24
24
|
export * from './nodeenrollment.js';
|
|
25
|
+
export * from './privatenetwork.js';
|
|
25
26
|
export * from './registry.js';
|
|
26
27
|
export * from './runtimeassignment.js';
|
|
27
28
|
export * from './runtimeconfig.js';
|
|
@@ -34,6 +35,7 @@ export * from './backup.js';
|
|
|
34
35
|
export * from './clusternode.js';
|
|
35
36
|
export * from './settings.js';
|
|
36
37
|
export * from './service.js';
|
|
38
|
+
export * from './servicecredential.js';
|
|
37
39
|
export * from './serviceports.js';
|
|
38
40
|
export * from './status.js';
|
|
39
41
|
export * from './taskexecution.js';
|
|
@@ -41,4 +43,4 @@ export * from './traffic.js';
|
|
|
41
43
|
export * from './user.js';
|
|
42
44
|
export * from './version.js';
|
|
43
45
|
export * from './webpush.js';
|
|
44
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
46
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxlQUFlLENBQUM7QUFDOUIsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMseUJBQXlCLENBQUM7QUFDeEMsY0FBYywwQkFBMEIsQ0FBQztBQUN6QyxjQUFjLDBCQUEwQixDQUFDO0FBQ3pDLGNBQWMsZ0JBQWdCLENBQUM7QUFDL0IsY0FBYyxVQUFVLENBQUM7QUFDekIsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxZQUFZLENBQUM7QUFDM0IsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLGdCQUFnQixDQUFDO0FBQy9CLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxzQkFBc0IsQ0FBQztBQUNyQyxjQUFjLDZCQUE2QixDQUFDO0FBQzVDLGNBQWMsV0FBVyxDQUFDO0FBQzFCLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLHFCQUFxQixDQUFDO0FBQ3BDLGNBQWMsZUFBZSxDQUFDO0FBQzlCLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLHFCQUFxQixDQUFDO0FBQ3BDLGNBQWMsNkJBQTZCLENBQUM7QUFDNUMsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGtCQUFrQixDQUFDO0FBQ2pDLGNBQWMsZUFBZSxDQUFDO0FBQzlCLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsY0FBYyxtQkFBbUIsQ0FBQztBQUNsQyxjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLG9CQUFvQixDQUFDO0FBQ25DLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsV0FBVyxDQUFDO0FBQzFCLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsY0FBYyxDQUFDIn0=
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Cloudly owns these definitions. Membership never follows node placement. */
|
|
2
|
+
export interface IPrivateNetworkPolicyV1 {
|
|
3
|
+
connectivity: 'member-full-mesh';
|
|
4
|
+
/** null denies external resolution; private names are never forwarded. */
|
|
5
|
+
externalDns: {
|
|
6
|
+
upstreams: Array<{
|
|
7
|
+
address: string;
|
|
8
|
+
port: number;
|
|
9
|
+
}>;
|
|
10
|
+
/** true requires an empty suffix list; false requires explicit suffixes. */
|
|
11
|
+
allowAnyPublicName: boolean;
|
|
12
|
+
permittedSuffixes: string[];
|
|
13
|
+
} | null;
|
|
14
|
+
}
|
|
15
|
+
export interface IPrivateNetworkV1 {
|
|
16
|
+
schemaVersion: 1;
|
|
17
|
+
id: string;
|
|
18
|
+
organizationId: string;
|
|
19
|
+
/** Immutable server-owned keys; display-name changes do not rename DNS. */
|
|
20
|
+
organizationDnsKey: string;
|
|
21
|
+
networkDnsKey: string;
|
|
22
|
+
name: string;
|
|
23
|
+
revision: number;
|
|
24
|
+
state: 'active' | 'retiring' | 'retired';
|
|
25
|
+
policy: IPrivateNetworkPolicyV1;
|
|
26
|
+
createdAt: number;
|
|
27
|
+
updatedAt: number;
|
|
28
|
+
}
|
|
29
|
+
export interface IPrivateNetworkAttachmentV1 {
|
|
30
|
+
networkId: string;
|
|
31
|
+
/** Sorted unique single-label aliases, reserved per network for this service. */
|
|
32
|
+
aliases: string[];
|
|
33
|
+
}
|
|
34
|
+
/** Desired membership only. Acceptance is neither packet enforcement nor DNS readiness. */
|
|
35
|
+
export interface IServicePrivateNetworksV1 {
|
|
36
|
+
schemaVersion: 1;
|
|
37
|
+
organizationId: string;
|
|
38
|
+
serviceId: string;
|
|
39
|
+
revision: number;
|
|
40
|
+
/** Sorted by networkId; every network must have the same canonical organization. */
|
|
41
|
+
attachments: IPrivateNetworkAttachmentV1[];
|
|
42
|
+
/** Exactly the sole attachment, or explicit selection/null when several exist. */
|
|
43
|
+
defaultNetworkId: string | null;
|
|
44
|
+
}
|
|
45
|
+
interface IPrivateNetworkMutationBaseV1 {
|
|
46
|
+
schemaVersion: 1;
|
|
47
|
+
organizationId: string;
|
|
48
|
+
/** Retain the exact body and this identifier across retries. */
|
|
49
|
+
mutationId: string;
|
|
50
|
+
}
|
|
51
|
+
export type TPrivateNetworkMutationV1 = IPrivateNetworkMutationBaseV1 & ({
|
|
52
|
+
operation: 'create';
|
|
53
|
+
expectedRevision: null;
|
|
54
|
+
name: string;
|
|
55
|
+
policy: IPrivateNetworkPolicyV1;
|
|
56
|
+
} | {
|
|
57
|
+
operation: 'update';
|
|
58
|
+
networkId: string;
|
|
59
|
+
expectedRevision: number;
|
|
60
|
+
name: string;
|
|
61
|
+
policy: IPrivateNetworkPolicyV1;
|
|
62
|
+
}
|
|
63
|
+
/** Retirement completes only after outstanding grants are denied or independently fenced. */
|
|
64
|
+
| {
|
|
65
|
+
operation: 'retire';
|
|
66
|
+
networkId: string;
|
|
67
|
+
expectedRevision: number;
|
|
68
|
+
});
|
|
69
|
+
export interface ISetServicePrivateNetworksV1 extends IPrivateNetworkMutationBaseV1 {
|
|
70
|
+
serviceId: string;
|
|
71
|
+
/** null means the service has never had a membership document; not an empty list. */
|
|
72
|
+
expectedRevision: number | null;
|
|
73
|
+
attachments: IPrivateNetworkAttachmentV1[];
|
|
74
|
+
defaultNetworkId: string | null;
|
|
75
|
+
}
|
|
76
|
+
export declare const privateNetworkV1Contract: Readonly<{
|
|
77
|
+
readonly privateRoot: "internal.";
|
|
78
|
+
readonly maximumAttachments: 16;
|
|
79
|
+
readonly maximumAliasesPerAttachment: 32;
|
|
80
|
+
readonly maximumUpstreams: 4;
|
|
81
|
+
readonly maximumPermittedSuffixes: 64;
|
|
82
|
+
readonly maximumDnsLeaseMs: 900000;
|
|
83
|
+
readonly maximumPositiveTtlSeconds: 5;
|
|
84
|
+
readonly maximumNegativeTtlSeconds: 1;
|
|
85
|
+
readonly packetAuthorityLifetime: "until-applied-denial-or-independent-fence";
|
|
86
|
+
readonly omission: "no-effect";
|
|
87
|
+
}>;
|
|
88
|
+
/** Structural validation only; backend IP ownership/reachability and policy fences are required. */
|
|
89
|
+
export declare const snapshotPrivateNetworkPolicyV1: (value: unknown) => IPrivateNetworkPolicyV1;
|
|
90
|
+
export declare const snapshotPrivateNetworkV1: (value: unknown) => IPrivateNetworkV1;
|
|
91
|
+
export declare const snapshotServicePrivateNetworksV1: (value: unknown) => IServicePrivateNetworksV1;
|
|
92
|
+
export declare const snapshotSetServicePrivateNetworksV1: (value: unknown) => ISetServicePrivateNetworksV1;
|
|
93
|
+
export declare const snapshotPrivateNetworkMutationV1: (value: unknown) => TPrivateNetworkMutationV1;
|
|
94
|
+
/** Pure naming helper; it does not reserve a name or create any public DNS/ingress record. */
|
|
95
|
+
export declare const buildPrivateNetworkFqdnV1: (networkValue: unknown, aliasValue: unknown) => string;
|
|
96
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
export const privateNetworkV1Contract = Object.freeze({
|
|
2
|
+
privateRoot: 'internal.',
|
|
3
|
+
maximumAttachments: 16,
|
|
4
|
+
maximumAliasesPerAttachment: 32,
|
|
5
|
+
maximumUpstreams: 4,
|
|
6
|
+
maximumPermittedSuffixes: 64,
|
|
7
|
+
maximumDnsLeaseMs: 900_000,
|
|
8
|
+
maximumPositiveTtlSeconds: 5,
|
|
9
|
+
maximumNegativeTtlSeconds: 1,
|
|
10
|
+
packetAuthorityLifetime: 'until-applied-denial-or-independent-fence',
|
|
11
|
+
omission: 'no-effect',
|
|
12
|
+
});
|
|
13
|
+
const fail = () => { throw new Error('Private network contract rejected.'); };
|
|
14
|
+
function requireValue(value) { if (!value)
|
|
15
|
+
fail(); }
|
|
16
|
+
const safe = (operation) => { try {
|
|
17
|
+
return operation();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return fail();
|
|
21
|
+
} };
|
|
22
|
+
const object = (value, keys) => {
|
|
23
|
+
requireValue(value !== null && typeof value === 'object' && !Array.isArray(value));
|
|
24
|
+
requireValue(Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
25
|
+
const actual = Reflect.ownKeys(value);
|
|
26
|
+
requireValue(actual.length === keys.length && keys.every(key => actual.includes(key)));
|
|
27
|
+
const result = Object.create(null);
|
|
28
|
+
for (const key of keys) {
|
|
29
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
30
|
+
requireValue(descriptor?.enumerable && Object.hasOwn(descriptor, 'value'));
|
|
31
|
+
result[key] = descriptor.value;
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
const array = (value, maximum, read) => {
|
|
36
|
+
requireValue(Array.isArray(value) && Object.getPrototypeOf(value) === Array.prototype && value.length <= maximum);
|
|
37
|
+
requireValue(Reflect.ownKeys(value).length === value.length + 1);
|
|
38
|
+
const result = [];
|
|
39
|
+
for (let index = 0; index < value.length; index++) {
|
|
40
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
41
|
+
requireValue(descriptor?.enumerable && Object.hasOwn(descriptor, 'value'));
|
|
42
|
+
result.push(read(descriptor.value));
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
const identifier = (value) => {
|
|
47
|
+
requireValue(typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/.test(value));
|
|
48
|
+
return value;
|
|
49
|
+
};
|
|
50
|
+
const counter = (value) => {
|
|
51
|
+
requireValue(typeof value === 'number' && Number.isSafeInteger(value) && value > 0 && value < Number.MAX_SAFE_INTEGER);
|
|
52
|
+
return value;
|
|
53
|
+
};
|
|
54
|
+
const name = (value) => {
|
|
55
|
+
requireValue(typeof value === 'string' && value.length > 0 && value.length <= 128
|
|
56
|
+
&& value === value.trim() && !/[\u0000-\u001f\u007f-\u009f\ud800-\udfff]/u.test(value));
|
|
57
|
+
return value;
|
|
58
|
+
};
|
|
59
|
+
const dnsKey = (value, prefix) => {
|
|
60
|
+
requireValue(typeof value === 'string' && new RegExp(`^${prefix}-[a-z2-7]{26}$`).test(value));
|
|
61
|
+
return value;
|
|
62
|
+
};
|
|
63
|
+
const label = (value) => {
|
|
64
|
+
requireValue(typeof value === 'string' && value.length <= 63
|
|
65
|
+
&& /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(value)
|
|
66
|
+
&& !['localhost', 'internal', 'resolver', 'ns', 'ns1', 'hostmaster'].includes(value));
|
|
67
|
+
return value;
|
|
68
|
+
};
|
|
69
|
+
const sorted = (values) => {
|
|
70
|
+
requireValue(values.every((value, index) => index === 0 || values[index - 1] < value));
|
|
71
|
+
return values;
|
|
72
|
+
};
|
|
73
|
+
const ipv4 = (value) => {
|
|
74
|
+
requireValue(typeof value === 'string');
|
|
75
|
+
const parts = value.split('.');
|
|
76
|
+
requireValue(parts.length === 4 && parts.every(part => /^(?:0|[1-9][0-9]{0,2})$/.test(part) && Number(part) <= 255));
|
|
77
|
+
requireValue(Number(parts[0]) > 0 && Number(parts[0]) < 224 && value !== '255.255.255.255');
|
|
78
|
+
return value;
|
|
79
|
+
};
|
|
80
|
+
const publicSuffix = (value) => {
|
|
81
|
+
requireValue(typeof value === 'string' && value.length <= 253 && value.endsWith('.'));
|
|
82
|
+
const labels = value.slice(0, -1).split('.');
|
|
83
|
+
requireValue(labels.length >= 2 && labels.every(part => part.length <= 63
|
|
84
|
+
&& /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(part))
|
|
85
|
+
&& !['internal', 'localhost', 'local', 'arpa'].includes(labels[labels.length - 1]));
|
|
86
|
+
return value;
|
|
87
|
+
};
|
|
88
|
+
/** Structural validation only; backend IP ownership/reachability and policy fences are required. */
|
|
89
|
+
export const snapshotPrivateNetworkPolicyV1 = (value) => safe(() => {
|
|
90
|
+
const policy = object(value, ['connectivity', 'externalDns']);
|
|
91
|
+
requireValue(policy.connectivity === 'member-full-mesh');
|
|
92
|
+
if (policy.externalDns === null)
|
|
93
|
+
return { connectivity: 'member-full-mesh', externalDns: null };
|
|
94
|
+
const external = object(policy.externalDns, ['upstreams', 'allowAnyPublicName', 'permittedSuffixes']);
|
|
95
|
+
const upstreams = array(external.upstreams, privateNetworkV1Contract.maximumUpstreams, entry => {
|
|
96
|
+
const upstream = object(entry, ['address', 'port']);
|
|
97
|
+
const port = counter(upstream.port);
|
|
98
|
+
requireValue(port <= 65535);
|
|
99
|
+
return { address: ipv4(upstream.address), port };
|
|
100
|
+
});
|
|
101
|
+
requireValue(upstreams.length > 0 && new Set(upstreams.map(upstream => `${upstream.address}:${upstream.port}`)).size === upstreams.length);
|
|
102
|
+
requireValue(typeof external.allowAnyPublicName === 'boolean');
|
|
103
|
+
const permittedSuffixes = sorted(array(external.permittedSuffixes, privateNetworkV1Contract.maximumPermittedSuffixes, publicSuffix));
|
|
104
|
+
requireValue(external.allowAnyPublicName ? permittedSuffixes.length === 0 : permittedSuffixes.length > 0);
|
|
105
|
+
return { connectivity: 'member-full-mesh', externalDns: {
|
|
106
|
+
upstreams, allowAnyPublicName: external.allowAnyPublicName, permittedSuffixes,
|
|
107
|
+
} };
|
|
108
|
+
});
|
|
109
|
+
const membership = (attachmentsValue, defaultValue) => {
|
|
110
|
+
const attachments = array(attachmentsValue, privateNetworkV1Contract.maximumAttachments, entry => {
|
|
111
|
+
const attachment = object(entry, ['networkId', 'aliases']);
|
|
112
|
+
return { networkId: identifier(attachment.networkId),
|
|
113
|
+
aliases: sorted(array(attachment.aliases, privateNetworkV1Contract.maximumAliasesPerAttachment, label)) };
|
|
114
|
+
});
|
|
115
|
+
sorted(attachments.map(attachment => attachment.networkId));
|
|
116
|
+
const defaultNetworkId = defaultValue === null ? null : identifier(defaultValue);
|
|
117
|
+
requireValue(defaultNetworkId === null || attachments.some(attachment => attachment.networkId === defaultNetworkId));
|
|
118
|
+
requireValue(attachments.length !== 1 || defaultNetworkId === attachments[0].networkId);
|
|
119
|
+
return { attachments, defaultNetworkId };
|
|
120
|
+
};
|
|
121
|
+
export const snapshotPrivateNetworkV1 = (value) => safe(() => {
|
|
122
|
+
const network = object(value, ['schemaVersion', 'id', 'organizationId', 'organizationDnsKey', 'networkDnsKey',
|
|
123
|
+
'name', 'revision', 'state', 'policy', 'createdAt', 'updatedAt']);
|
|
124
|
+
requireValue(network.schemaVersion === 1 && ['active', 'retiring', 'retired'].includes(network.state));
|
|
125
|
+
const createdAt = counter(network.createdAt);
|
|
126
|
+
const updatedAt = counter(network.updatedAt);
|
|
127
|
+
requireValue(updatedAt >= createdAt);
|
|
128
|
+
return { schemaVersion: 1, id: identifier(network.id), organizationId: identifier(network.organizationId),
|
|
129
|
+
organizationDnsKey: dnsKey(network.organizationDnsKey, 'o'), networkDnsKey: dnsKey(network.networkDnsKey, 'n'),
|
|
130
|
+
name: name(network.name), revision: counter(network.revision), state: network.state,
|
|
131
|
+
policy: snapshotPrivateNetworkPolicyV1(network.policy), createdAt, updatedAt };
|
|
132
|
+
});
|
|
133
|
+
export const snapshotServicePrivateNetworksV1 = (value) => safe(() => {
|
|
134
|
+
const service = object(value, ['schemaVersion', 'organizationId', 'serviceId', 'revision', 'attachments', 'defaultNetworkId']);
|
|
135
|
+
requireValue(service.schemaVersion === 1);
|
|
136
|
+
return { schemaVersion: 1, organizationId: identifier(service.organizationId), serviceId: identifier(service.serviceId),
|
|
137
|
+
revision: counter(service.revision), ...membership(service.attachments, service.defaultNetworkId) };
|
|
138
|
+
});
|
|
139
|
+
export const snapshotSetServicePrivateNetworksV1 = (value) => safe(() => {
|
|
140
|
+
const request = object(value, ['schemaVersion', 'organizationId', 'serviceId', 'mutationId', 'expectedRevision', 'attachments', 'defaultNetworkId']);
|
|
141
|
+
requireValue(request.schemaVersion === 1);
|
|
142
|
+
return { schemaVersion: 1, organizationId: identifier(request.organizationId), serviceId: identifier(request.serviceId),
|
|
143
|
+
mutationId: identifier(request.mutationId), expectedRevision: request.expectedRevision === null ? null : counter(request.expectedRevision),
|
|
144
|
+
...membership(request.attachments, request.defaultNetworkId) };
|
|
145
|
+
});
|
|
146
|
+
export const snapshotPrivateNetworkMutationV1 = (value) => safe(() => {
|
|
147
|
+
requireValue(value !== null && typeof value === 'object');
|
|
148
|
+
const operation = Object.getOwnPropertyDescriptor(value, 'operation')?.value;
|
|
149
|
+
requireValue(operation === 'create' || operation === 'update' || operation === 'retire');
|
|
150
|
+
const request = object(value, ['schemaVersion', 'organizationId', 'mutationId', 'operation', 'expectedRevision',
|
|
151
|
+
...(operation === 'create' ? [] : ['networkId']), ...(operation === 'retire' ? [] : ['name', 'policy'])]);
|
|
152
|
+
requireValue(request.schemaVersion === 1);
|
|
153
|
+
const base = { schemaVersion: 1, organizationId: identifier(request.organizationId), mutationId: identifier(request.mutationId) };
|
|
154
|
+
if (operation === 'create') {
|
|
155
|
+
requireValue(request.expectedRevision === null);
|
|
156
|
+
return { ...base, operation, expectedRevision: null, name: name(request.name), policy: snapshotPrivateNetworkPolicyV1(request.policy) };
|
|
157
|
+
}
|
|
158
|
+
const current = { networkId: identifier(request.networkId), expectedRevision: counter(request.expectedRevision) };
|
|
159
|
+
if (operation === 'retire')
|
|
160
|
+
return { ...base, ...current, operation };
|
|
161
|
+
return { ...base, ...current, operation, name: name(request.name), policy: snapshotPrivateNetworkPolicyV1(request.policy) };
|
|
162
|
+
});
|
|
163
|
+
/** Pure naming helper; it does not reserve a name or create any public DNS/ingress record. */
|
|
164
|
+
export const buildPrivateNetworkFqdnV1 = (networkValue, aliasValue) => safe(() => {
|
|
165
|
+
const network = snapshotPrivateNetworkV1(networkValue);
|
|
166
|
+
return `${label(aliasValue)}.${network.networkDnsKey}.${network.organizationDnsKey}.${privateNetworkV1Contract.privateRoot}`;
|
|
167
|
+
});
|
|
168
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpdmF0ZW5ldHdvcmsuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL3ByaXZhdGVuZXR3b3JrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQW1FQSxNQUFNLENBQUMsTUFBTSx3QkFBd0IsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ3BELFdBQVcsRUFBRSxXQUFXO0lBQ3hCLGtCQUFrQixFQUFFLEVBQUU7SUFDdEIsMkJBQTJCLEVBQUUsRUFBRTtJQUMvQixnQkFBZ0IsRUFBRSxDQUFDO0lBQ25CLHdCQUF3QixFQUFFLEVBQUU7SUFDNUIsaUJBQWlCLEVBQUUsT0FBTztJQUMxQix5QkFBeUIsRUFBRSxDQUFDO0lBQzVCLHlCQUF5QixFQUFFLENBQUM7SUFDNUIsdUJBQXVCLEVBQUUsMkNBQTJDO0lBQ3BFLFFBQVEsRUFBRSxXQUFXO0NBQ2IsQ0FBQyxDQUFDO0FBRVosTUFBTSxJQUFJLEdBQUcsR0FBVSxFQUFFLEdBQUcsTUFBTSxJQUFJLEtBQUssQ0FBQyxvQ0FBb0MsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3JGLFNBQVMsWUFBWSxDQUFDLEtBQWMsSUFBbUIsSUFBSSxDQUFDLEtBQUs7SUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUM7QUFDNUUsTUFBTSxJQUFJLEdBQUcsQ0FBSSxTQUFrQixFQUFLLEVBQUUsR0FBRyxJQUFJLENBQUM7SUFBQyxPQUFPLFNBQVMsRUFBRSxDQUFDO0FBQUMsQ0FBQztBQUFDLE1BQU0sQ0FBQztJQUFDLE9BQU8sSUFBSSxFQUFFLENBQUM7QUFBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3BHLE1BQU0sTUFBTSxHQUFHLENBQUMsS0FBYyxFQUFFLElBQWMsRUFBMkIsRUFBRTtJQUN6RSxZQUFZLENBQUMsS0FBSyxLQUFLLElBQUksSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7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
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Workload permissions are independent of human membership and deployment grants. */
|
|
2
|
+
export type TServiceMachineCapabilityV1 = 'platform:session';
|
|
3
|
+
/** Exact existing-service authority. Never infer this grant from an API role. */
|
|
4
|
+
export interface IServiceMachineGrantV1 {
|
|
5
|
+
organizationId: string;
|
|
6
|
+
serviceId: string;
|
|
7
|
+
capabilities: TServiceMachineCapabilityV1[];
|
|
8
|
+
}
|
|
9
|
+
/** Public metadata only. The bearer is delivered through the owning service's SecretSet. */
|
|
10
|
+
export interface IServiceMachineCredentialMetadataV1 {
|
|
11
|
+
schemaVersion: 1;
|
|
12
|
+
credentialId: string;
|
|
13
|
+
userId: string;
|
|
14
|
+
grant: IServiceMachineGrantV1;
|
|
15
|
+
generation: number;
|
|
16
|
+
state: 'active' | 'revoked';
|
|
17
|
+
expiresAt: number;
|
|
18
|
+
createdAt: number;
|
|
19
|
+
updatedAt: number;
|
|
20
|
+
/** Exact immutable encrypted delivery version; null after revocation. */
|
|
21
|
+
delivery: {
|
|
22
|
+
secretId: string;
|
|
23
|
+
secretVersionId: string;
|
|
24
|
+
} | null;
|
|
25
|
+
}
|
|
26
|
+
interface IServiceMachineCredentialMutationBaseV1 {
|
|
27
|
+
schemaVersion: 1;
|
|
28
|
+
organizationId: string;
|
|
29
|
+
serviceId: string;
|
|
30
|
+
/** Retain the complete request and this ID across retries. */
|
|
31
|
+
mutationId: string;
|
|
32
|
+
}
|
|
33
|
+
/** Creation is absent-only; rotation and revocation require the exact current generation. */
|
|
34
|
+
export type TServiceMachineCredentialMutationV1 = IServiceMachineCredentialMutationBaseV1 & ({
|
|
35
|
+
operation: 'ensure';
|
|
36
|
+
expectedGeneration: null;
|
|
37
|
+
capabilities: TServiceMachineCapabilityV1[];
|
|
38
|
+
expiresAt: number;
|
|
39
|
+
} | {
|
|
40
|
+
operation: 'rotate';
|
|
41
|
+
expectedGeneration: number;
|
|
42
|
+
capabilities: TServiceMachineCapabilityV1[];
|
|
43
|
+
expiresAt: number;
|
|
44
|
+
} | {
|
|
45
|
+
operation: 'revoke';
|
|
46
|
+
expectedGeneration: number;
|
|
47
|
+
});
|
|
48
|
+
export interface IServiceMachineCredentialMutationResultV1 {
|
|
49
|
+
/** Historical on replay; replay never restores revoked or superseded authority. */
|
|
50
|
+
credential: IServiceMachineCredentialMetadataV1;
|
|
51
|
+
replayed: boolean;
|
|
52
|
+
}
|
|
53
|
+
/** Structural copy only; the backend must fence live service ownership and credential authority. */
|
|
54
|
+
export declare const snapshotServiceMachineGrantV1: (value: unknown) => IServiceMachineGrantV1;
|
|
55
|
+
export declare const snapshotServiceMachineCredentialMutationV1: (value: unknown) => TServiceMachineCredentialMutationV1;
|
|
56
|
+
export declare const snapshotServiceMachineCredentialMetadataV1: (value: unknown) => IServiceMachineCredentialMetadataV1;
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const fail = () => { throw new Error('Service credential contract rejected.'); };
|
|
2
|
+
function requireValue(value) { if (!value)
|
|
3
|
+
fail(); }
|
|
4
|
+
const object = (value, keys) => {
|
|
5
|
+
requireValue(value !== null && typeof value === 'object' && !Array.isArray(value));
|
|
6
|
+
requireValue(Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
7
|
+
const actual = Reflect.ownKeys(value);
|
|
8
|
+
requireValue(actual.length === keys.length && keys.every((key) => actual.includes(key)));
|
|
9
|
+
const copy = Object.create(null);
|
|
10
|
+
for (const key of keys) {
|
|
11
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
12
|
+
requireValue(descriptor && Object.hasOwn(descriptor, 'value') && descriptor.enumerable);
|
|
13
|
+
copy[key] = descriptor.value;
|
|
14
|
+
}
|
|
15
|
+
return copy;
|
|
16
|
+
};
|
|
17
|
+
const identifier = (value) => {
|
|
18
|
+
requireValue(typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/.test(value));
|
|
19
|
+
return value;
|
|
20
|
+
};
|
|
21
|
+
const counter = (value) => {
|
|
22
|
+
requireValue(typeof value === 'number' && Number.isSafeInteger(value) && value > 0 && value < Number.MAX_SAFE_INTEGER);
|
|
23
|
+
return value;
|
|
24
|
+
};
|
|
25
|
+
const capabilities = (value) => {
|
|
26
|
+
requireValue(Array.isArray(value) && Object.getPrototypeOf(value) === Array.prototype);
|
|
27
|
+
const keys = Reflect.ownKeys(value);
|
|
28
|
+
requireValue(keys.length === 2 && keys.includes('length') && keys.includes('0'));
|
|
29
|
+
const entry = Object.getOwnPropertyDescriptor(value, '0');
|
|
30
|
+
requireValue(entry && Object.hasOwn(entry, 'value') && entry.enumerable && entry.value === 'platform:session');
|
|
31
|
+
return ['platform:session'];
|
|
32
|
+
};
|
|
33
|
+
const safe = (operation) => { try {
|
|
34
|
+
return operation();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return fail();
|
|
38
|
+
} };
|
|
39
|
+
/** Structural copy only; the backend must fence live service ownership and credential authority. */
|
|
40
|
+
export const snapshotServiceMachineGrantV1 = (value) => safe(() => {
|
|
41
|
+
const grant = object(value, ['organizationId', 'serviceId', 'capabilities']);
|
|
42
|
+
return { organizationId: identifier(grant.organizationId), serviceId: identifier(grant.serviceId),
|
|
43
|
+
capabilities: capabilities(grant.capabilities) };
|
|
44
|
+
});
|
|
45
|
+
export const snapshotServiceMachineCredentialMutationV1 = (value) => safe(() => {
|
|
46
|
+
requireValue(value !== null && typeof value === 'object');
|
|
47
|
+
const operation = Object.getOwnPropertyDescriptor(value, 'operation')?.value;
|
|
48
|
+
requireValue(operation === 'ensure' || operation === 'rotate' || operation === 'revoke');
|
|
49
|
+
const request = object(value, ['schemaVersion', 'organizationId', 'serviceId', 'mutationId', 'operation',
|
|
50
|
+
'expectedGeneration', ...(operation === 'revoke' ? [] : ['capabilities', 'expiresAt'])]);
|
|
51
|
+
requireValue(request.schemaVersion === 1);
|
|
52
|
+
const base = { schemaVersion: 1, organizationId: identifier(request.organizationId),
|
|
53
|
+
serviceId: identifier(request.serviceId), mutationId: identifier(request.mutationId) };
|
|
54
|
+
if (operation === 'revoke')
|
|
55
|
+
return { ...base, operation, expectedGeneration: counter(request.expectedGeneration) };
|
|
56
|
+
const material = { capabilities: capabilities(request.capabilities), expiresAt: counter(request.expiresAt) };
|
|
57
|
+
if (operation === 'ensure') {
|
|
58
|
+
requireValue(request.expectedGeneration === null);
|
|
59
|
+
return { ...base, ...material, operation, expectedGeneration: null };
|
|
60
|
+
}
|
|
61
|
+
return { ...base, ...material, operation, expectedGeneration: counter(request.expectedGeneration) };
|
|
62
|
+
});
|
|
63
|
+
export const snapshotServiceMachineCredentialMetadataV1 = (value) => safe(() => {
|
|
64
|
+
const metadata = object(value, ['schemaVersion', 'credentialId', 'userId', 'grant', 'generation', 'state',
|
|
65
|
+
'expiresAt', 'createdAt', 'updatedAt', 'delivery']);
|
|
66
|
+
requireValue(metadata.schemaVersion === 1 && (metadata.state === 'active' || metadata.state === 'revoked'));
|
|
67
|
+
const createdAt = counter(metadata.createdAt);
|
|
68
|
+
const updatedAt = counter(metadata.updatedAt);
|
|
69
|
+
requireValue(updatedAt >= createdAt);
|
|
70
|
+
let delivery = null;
|
|
71
|
+
if (metadata.state === 'active') {
|
|
72
|
+
const version = object(metadata.delivery, ['secretId', 'secretVersionId']);
|
|
73
|
+
delivery = { secretId: identifier(version.secretId), secretVersionId: identifier(version.secretVersionId) };
|
|
74
|
+
}
|
|
75
|
+
else
|
|
76
|
+
requireValue(metadata.delivery === null);
|
|
77
|
+
return { schemaVersion: 1, credentialId: identifier(metadata.credentialId), userId: identifier(metadata.userId),
|
|
78
|
+
grant: snapshotServiceMachineGrantV1(metadata.grant), generation: counter(metadata.generation),
|
|
79
|
+
state: metadata.state, expiresAt: counter(metadata.expiresAt), createdAt, updatedAt, delivery };
|
|
80
|
+
});
|
|
81
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZWNyZWRlbnRpYWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL3NlcnZpY2VjcmVkZW50aWFsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQXdEQSxNQUFNLElBQUksR0FBRyxHQUFVLEVBQUUsR0FBRyxNQUFNLElBQUksS0FBSyxDQUFDLHVDQUF1QyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDeEYsU0FBUyxZQUFZLENBQUMsS0FBYyxJQUFtQixJQUFJLENBQUMsS0FBSztJQUFFLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQztBQUM1RSxNQUFNLE1BQU0sR0FBRyxDQUFDLEtBQWMsRUFBRSxJQUFjLEVBQTJCLEVBQUU7SUFDekUsWUFBWSxDQUFDLEtBQUssS0FBSyxJQUFJLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO0lBQ25GLFlBQVksQ0FBQyxNQUFNLENBQUMsY0FBYyxDQUFDLEtBQUssQ0FBQyxLQUFLLE1BQU0sQ0FBQyxTQUFTLElBQUksTUFBTSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsS0FBSyxJQUFJLENBQUMsQ0FBQztJQUN6RyxNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3RDLFlBQVksQ0FBQyxNQUFNLENBQUMsTUFBTSxLQUFLLElBQUksQ0FBQyxNQUFNLElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDekYsTUFBTSxJQUFJLEdBQTRCLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDMUQsS0FBSyxNQUFNLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQztRQUN2QixNQUFNLFVBQVUsR0FBRyxNQUFN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
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
2
|
import type { IIdentity, IIdentityCredential, IServiceDeploymentGrant } from '../data/user.js';
|
|
3
|
+
import type { IServiceMachineCredentialMetadataV1, IServiceMachineCredentialMutationResultV1, TServiceMachineCredentialMutationV1 } from '../data/servicecredential.js';
|
|
4
|
+
export interface IReq_Admin_GetServiceMachineCredentialV1 extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_GetServiceMachineCredentialV1> {
|
|
5
|
+
method: 'getServiceMachineCredentialV1';
|
|
6
|
+
request: {
|
|
7
|
+
identity: IIdentityCredential;
|
|
8
|
+
organizationId: string;
|
|
9
|
+
serviceId: string;
|
|
10
|
+
};
|
|
11
|
+
response: {
|
|
12
|
+
credential: IServiceMachineCredentialMetadataV1 | null;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The backend authorizes the exact service and mutates hashed credential authority,
|
|
17
|
+
* encrypted SecretSet delivery and the replay receipt in one fenced transaction.
|
|
18
|
+
* No bearer is returned. Existing legacy deployment/cluster credentials are separate.
|
|
19
|
+
*/
|
|
20
|
+
export interface IReq_Admin_MutateServiceMachineCredentialV1 extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_MutateServiceMachineCredentialV1> {
|
|
21
|
+
method: 'mutateServiceMachineCredentialV1';
|
|
22
|
+
request: {
|
|
23
|
+
identity: IIdentityCredential;
|
|
24
|
+
mutation: TServiceMachineCredentialMutationV1;
|
|
25
|
+
};
|
|
26
|
+
response: IServiceMachineCredentialMutationResultV1;
|
|
27
|
+
}
|
|
3
28
|
/** Backend-owned human credential metadata; never includes a password verifier. */
|
|
4
29
|
export interface IHumanCredentialMetadata {
|
|
5
30
|
userId: string;
|
|
@@ -1,4 +1,66 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
|
+
import type { IIdentityCredential } from '../data/user.js';
|
|
3
|
+
import type { IPrivateNetworkV1, IServicePrivateNetworksV1, ISetServicePrivateNetworksV1, TPrivateNetworkMutationV1 } from '../data/privatenetwork.js';
|
|
4
|
+
/** Every request is authorized against live canonical membership and Cloudly policy. */
|
|
5
|
+
export interface IReq_Cloudly_ListPrivateNetworksV1 extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_ListPrivateNetworksV1> {
|
|
6
|
+
method: 'listPrivateNetworksV1';
|
|
7
|
+
request: {
|
|
8
|
+
identity: IIdentityCredential;
|
|
9
|
+
organizationId: string;
|
|
10
|
+
cursor: string | null;
|
|
11
|
+
limit: number;
|
|
12
|
+
};
|
|
13
|
+
response: {
|
|
14
|
+
networks: IPrivateNetworkV1[];
|
|
15
|
+
nextCursor: string | null;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export interface IReq_Cloudly_GetPrivateNetworkV1 extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_GetPrivateNetworkV1> {
|
|
19
|
+
method: 'getPrivateNetworkV1';
|
|
20
|
+
request: {
|
|
21
|
+
identity: IIdentityCredential;
|
|
22
|
+
organizationId: string;
|
|
23
|
+
networkId: string;
|
|
24
|
+
};
|
|
25
|
+
response: {
|
|
26
|
+
network: IPrivateNetworkV1 | null;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface IReq_Cloudly_MutatePrivateNetworkV1 extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_MutatePrivateNetworkV1> {
|
|
30
|
+
method: 'mutatePrivateNetworkV1';
|
|
31
|
+
request: {
|
|
32
|
+
identity: IIdentityCredential;
|
|
33
|
+
mutation: TPrivateNetworkMutationV1;
|
|
34
|
+
};
|
|
35
|
+
/** Historical on replay; neither acceptance nor replay proves applied packet/DNS state. */
|
|
36
|
+
response: {
|
|
37
|
+
network: IPrivateNetworkV1;
|
|
38
|
+
replayed: boolean;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export interface IReq_Cloudly_GetServicePrivateNetworksV1 extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_GetServicePrivateNetworksV1> {
|
|
42
|
+
method: 'getServicePrivateNetworksV1';
|
|
43
|
+
request: {
|
|
44
|
+
identity: IIdentityCredential;
|
|
45
|
+
organizationId: string;
|
|
46
|
+
serviceId: string;
|
|
47
|
+
};
|
|
48
|
+
response: {
|
|
49
|
+
membership: IServicePrivateNetworksV1 | null;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export interface IReq_Cloudly_SetServicePrivateNetworksV1 extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_SetServicePrivateNetworksV1> {
|
|
53
|
+
method: 'setServicePrivateNetworksV1';
|
|
54
|
+
request: {
|
|
55
|
+
identity: IIdentityCredential;
|
|
56
|
+
mutation: ISetServicePrivateNetworksV1;
|
|
57
|
+
};
|
|
58
|
+
/** Exact desired membership; enforcement and outstanding revocations remain separately observed. */
|
|
59
|
+
response: {
|
|
60
|
+
membership: IServicePrivateNetworksV1;
|
|
61
|
+
replayed: boolean;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
2
64
|
export interface IRequest_Any_Cloudly_GetNetworkNodes {
|
|
3
65
|
method: 'getNetworkNodes';
|
|
4
66
|
request: {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serve.zone/interfaces",
|
|
3
|
-
"version": "30.
|
|
3
|
+
"version": "30.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
|
|
6
6
|
"exports": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@git.zone/tsbuild": "^4.4.2",
|
|
31
31
|
"@git.zone/tsdoc": "^2.2.0",
|
|
32
32
|
"@git.zone/tsrun": "^2.0.6",
|
|
33
|
-
"@git.zone/tstest": "^4.
|
|
33
|
+
"@git.zone/tstest": "^4.1.1",
|
|
34
34
|
"@types/node": "26.4.1"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
package/readme.md
CHANGED
|
@@ -174,6 +174,50 @@ bounded time/concurrency, root-only directory/socket permissions and drained
|
|
|
174
174
|
cleanup. The package supplies no listener, permission checks or persistent state.
|
|
175
175
|
No bearer, generic rotation operation or database access is part of this IPC API.
|
|
176
176
|
|
|
177
|
+
### Service Machine Credentials
|
|
178
|
+
|
|
179
|
+
`requests.admin.getServiceMachineCredentialV1` inspects redacted metadata;
|
|
180
|
+
`mutateServiceMachineCredentialV1` creates, rotates or revokes authority for one
|
|
181
|
+
exact existing service and organization. `ensure` requires absent authority and
|
|
182
|
+
a null expected generation. Rotation and revocation require the exact current
|
|
183
|
+
generation. Retain the mutation ID and complete input for retry; a receipt replay
|
|
184
|
+
returns historical metadata without restoring authority.
|
|
185
|
+
|
|
186
|
+
`IServiceMachineGrantV1` currently grants only `platform:session`. This is separate
|
|
187
|
+
from deployment grants, human membership and infrastructure permissions. The
|
|
188
|
+
backend must authenticate each JWT against the current active credential
|
|
189
|
+
generation, expiry and exact service ownership. Structural snapshot helpers do
|
|
190
|
+
not perform these checks. Credential changes, encrypted service SecretSet
|
|
191
|
+
delivery and the replay receipt must commit in the same authorized transaction.
|
|
192
|
+
The response contains only the immutable secret/version reference; no bearer or
|
|
193
|
+
hash is returned. Revocation removes current delivery from the metadata.
|
|
194
|
+
|
|
195
|
+
### Private Networks
|
|
196
|
+
|
|
197
|
+
`requests.network` defines organization-scoped network create/update/retire and
|
|
198
|
+
service attachment operations. Mutations require a retained idempotency ID and
|
|
199
|
+
an exact expected revision; `null` means no prior document. Cloudly must authorize
|
|
200
|
+
each request against live canonical membership and policy, reserve aliases
|
|
201
|
+
transactionally, reject cross-organization attachments, and retain historical
|
|
202
|
+
receipts. These contracts do not implement the backend or node enforcement.
|
|
203
|
+
|
|
204
|
+
`data.buildPrivateNetworkFqdnV1` produces
|
|
205
|
+
`<alias>.n-<26 lowercase base32 characters>.o-<26 lowercase base32 characters>.internal.`
|
|
206
|
+
from server-owned immutable DNS keys. Aliases are lowercase ASCII labels unique
|
|
207
|
+
per network. Display-name edits do not rename DNS. A sole attachment selects its
|
|
208
|
+
search suffix; several attachments require an explicit default or no search
|
|
209
|
+
suffix. Fully qualified names remain unambiguous across networks. Network policy
|
|
210
|
+
permits member-to-member connectivity and explicitly configures or denies external
|
|
211
|
+
DNS forwarding. Creating an alias does not publish public DNS, ingress or ports.
|
|
212
|
+
|
|
213
|
+
The snapshot helpers validate and detach content; they do not authenticate it,
|
|
214
|
+
reserve names, prove membership, or report applied state. Packet authority remains
|
|
215
|
+
until an applied denial or independent fence. DNS snapshots have a separate maximum
|
|
216
|
+
15-minute validity, with positive TTLs capped at five seconds and negative TTLs at
|
|
217
|
+
one second. DNS expiry does not prove packet revocation or permit identity reuse.
|
|
218
|
+
Retirement and attachment responses describe desired or historical metadata;
|
|
219
|
+
consumers must separately track outstanding node revocations and readiness.
|
|
220
|
+
|
|
177
221
|
### Node-Bound Runtime Assignments
|
|
178
222
|
|
|
179
223
|
The `data` namespace exports `IRuntimeAssignmentV1`, canonical digest helpers,
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/data/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from './isolatedrestore.golden.js';
|
|
|
22
22
|
export * from './mail.js';
|
|
23
23
|
export * from './nodecredential.js';
|
|
24
24
|
export * from './nodeenrollment.js';
|
|
25
|
+
export * from './privatenetwork.js';
|
|
25
26
|
export * from './registry.js';
|
|
26
27
|
export * from './runtimeassignment.js';
|
|
27
28
|
export * from './runtimeconfig.js';
|
|
@@ -34,6 +35,7 @@ export * from './backup.js';
|
|
|
34
35
|
export * from './clusternode.js';
|
|
35
36
|
export * from './settings.js';
|
|
36
37
|
export * from './service.js';
|
|
38
|
+
export * from './servicecredential.js';
|
|
37
39
|
export * from './serviceports.js';
|
|
38
40
|
export * from './status.js';
|
|
39
41
|
export * from './taskexecution.js';
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/** Cloudly owns these definitions. Membership never follows node placement. */
|
|
2
|
+
export interface IPrivateNetworkPolicyV1 {
|
|
3
|
+
connectivity: 'member-full-mesh';
|
|
4
|
+
/** null denies external resolution; private names are never forwarded. */
|
|
5
|
+
externalDns: {
|
|
6
|
+
upstreams: Array<{ address: string; port: number }>;
|
|
7
|
+
/** true requires an empty suffix list; false requires explicit suffixes. */
|
|
8
|
+
allowAnyPublicName: boolean;
|
|
9
|
+
permittedSuffixes: string[];
|
|
10
|
+
} | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface IPrivateNetworkV1 {
|
|
14
|
+
schemaVersion: 1;
|
|
15
|
+
id: string;
|
|
16
|
+
organizationId: string;
|
|
17
|
+
/** Immutable server-owned keys; display-name changes do not rename DNS. */
|
|
18
|
+
organizationDnsKey: string;
|
|
19
|
+
networkDnsKey: string;
|
|
20
|
+
name: string;
|
|
21
|
+
revision: number;
|
|
22
|
+
state: 'active' | 'retiring' | 'retired';
|
|
23
|
+
policy: IPrivateNetworkPolicyV1;
|
|
24
|
+
createdAt: number;
|
|
25
|
+
updatedAt: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface IPrivateNetworkAttachmentV1 {
|
|
29
|
+
networkId: string;
|
|
30
|
+
/** Sorted unique single-label aliases, reserved per network for this service. */
|
|
31
|
+
aliases: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Desired membership only. Acceptance is neither packet enforcement nor DNS readiness. */
|
|
35
|
+
export interface IServicePrivateNetworksV1 {
|
|
36
|
+
schemaVersion: 1;
|
|
37
|
+
organizationId: string;
|
|
38
|
+
serviceId: string;
|
|
39
|
+
revision: number;
|
|
40
|
+
/** Sorted by networkId; every network must have the same canonical organization. */
|
|
41
|
+
attachments: IPrivateNetworkAttachmentV1[];
|
|
42
|
+
/** Exactly the sole attachment, or explicit selection/null when several exist. */
|
|
43
|
+
defaultNetworkId: string | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface IPrivateNetworkMutationBaseV1 {
|
|
47
|
+
schemaVersion: 1;
|
|
48
|
+
organizationId: string;
|
|
49
|
+
/** Retain the exact body and this identifier across retries. */
|
|
50
|
+
mutationId: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type TPrivateNetworkMutationV1 = IPrivateNetworkMutationBaseV1 & (
|
|
54
|
+
| { operation: 'create'; expectedRevision: null; name: string; policy: IPrivateNetworkPolicyV1 }
|
|
55
|
+
| { operation: 'update'; networkId: string; expectedRevision: number; name: string; policy: IPrivateNetworkPolicyV1 }
|
|
56
|
+
/** Retirement completes only after outstanding grants are denied or independently fenced. */
|
|
57
|
+
| { operation: 'retire'; networkId: string; expectedRevision: number }
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export interface ISetServicePrivateNetworksV1 extends IPrivateNetworkMutationBaseV1 {
|
|
61
|
+
serviceId: string;
|
|
62
|
+
/** null means the service has never had a membership document; not an empty list. */
|
|
63
|
+
expectedRevision: number | null;
|
|
64
|
+
attachments: IPrivateNetworkAttachmentV1[];
|
|
65
|
+
defaultNetworkId: string | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const privateNetworkV1Contract = Object.freeze({
|
|
69
|
+
privateRoot: 'internal.',
|
|
70
|
+
maximumAttachments: 16,
|
|
71
|
+
maximumAliasesPerAttachment: 32,
|
|
72
|
+
maximumUpstreams: 4,
|
|
73
|
+
maximumPermittedSuffixes: 64,
|
|
74
|
+
maximumDnsLeaseMs: 900_000,
|
|
75
|
+
maximumPositiveTtlSeconds: 5,
|
|
76
|
+
maximumNegativeTtlSeconds: 1,
|
|
77
|
+
packetAuthorityLifetime: 'until-applied-denial-or-independent-fence',
|
|
78
|
+
omission: 'no-effect',
|
|
79
|
+
} as const);
|
|
80
|
+
|
|
81
|
+
const fail = (): never => { throw new Error('Private network contract rejected.'); };
|
|
82
|
+
function requireValue(value: unknown): asserts value { if (!value) fail(); }
|
|
83
|
+
const safe = <T>(operation: () => T): T => { try { return operation(); } catch { return fail(); } };
|
|
84
|
+
const object = (value: unknown, keys: string[]): Record<string, unknown> => {
|
|
85
|
+
requireValue(value !== null && typeof value === 'object' && !Array.isArray(value));
|
|
86
|
+
requireValue(Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
87
|
+
const actual = Reflect.ownKeys(value);
|
|
88
|
+
requireValue(actual.length === keys.length && keys.every(key => actual.includes(key)));
|
|
89
|
+
const result: Record<string, unknown> = Object.create(null);
|
|
90
|
+
for (const key of keys) {
|
|
91
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
92
|
+
requireValue(descriptor?.enumerable && Object.hasOwn(descriptor, 'value'));
|
|
93
|
+
result[key] = descriptor.value;
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
};
|
|
97
|
+
const array = <T>(value: unknown, maximum: number, read: (item: unknown) => T): T[] => {
|
|
98
|
+
requireValue(Array.isArray(value) && Object.getPrototypeOf(value) === Array.prototype && value.length <= maximum);
|
|
99
|
+
requireValue(Reflect.ownKeys(value).length === value.length + 1);
|
|
100
|
+
const result: T[] = [];
|
|
101
|
+
for (let index = 0; index < value.length; index++) {
|
|
102
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
103
|
+
requireValue(descriptor?.enumerable && Object.hasOwn(descriptor, 'value'));
|
|
104
|
+
result.push(read(descriptor.value));
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
};
|
|
108
|
+
const identifier = (value: unknown): string => {
|
|
109
|
+
requireValue(typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/.test(value));
|
|
110
|
+
return value;
|
|
111
|
+
};
|
|
112
|
+
const counter = (value: unknown): number => {
|
|
113
|
+
requireValue(typeof value === 'number' && Number.isSafeInteger(value) && value > 0 && value < Number.MAX_SAFE_INTEGER);
|
|
114
|
+
return value;
|
|
115
|
+
};
|
|
116
|
+
const name = (value: unknown): string => {
|
|
117
|
+
requireValue(typeof value === 'string' && value.length > 0 && value.length <= 128
|
|
118
|
+
&& value === value.trim() && !/[\u0000-\u001f\u007f-\u009f\ud800-\udfff]/u.test(value));
|
|
119
|
+
return value;
|
|
120
|
+
};
|
|
121
|
+
const dnsKey = (value: unknown, prefix: 'o' | 'n'): string => {
|
|
122
|
+
requireValue(typeof value === 'string' && new RegExp(`^${prefix}-[a-z2-7]{26}$`).test(value));
|
|
123
|
+
return value;
|
|
124
|
+
};
|
|
125
|
+
const label = (value: unknown): string => {
|
|
126
|
+
requireValue(typeof value === 'string' && value.length <= 63
|
|
127
|
+
&& /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(value)
|
|
128
|
+
&& !['localhost', 'internal', 'resolver', 'ns', 'ns1', 'hostmaster'].includes(value));
|
|
129
|
+
return value;
|
|
130
|
+
};
|
|
131
|
+
const sorted = (values: string[]): string[] => {
|
|
132
|
+
requireValue(values.every((value, index) => index === 0 || values[index - 1] < value));
|
|
133
|
+
return values;
|
|
134
|
+
};
|
|
135
|
+
const ipv4 = (value: unknown): string => {
|
|
136
|
+
requireValue(typeof value === 'string');
|
|
137
|
+
const parts = value.split('.');
|
|
138
|
+
requireValue(parts.length === 4 && parts.every(part => /^(?:0|[1-9][0-9]{0,2})$/.test(part) && Number(part) <= 255));
|
|
139
|
+
requireValue(Number(parts[0]) > 0 && Number(parts[0]) < 224 && value !== '255.255.255.255');
|
|
140
|
+
return value;
|
|
141
|
+
};
|
|
142
|
+
const publicSuffix = (value: unknown): string => {
|
|
143
|
+
requireValue(typeof value === 'string' && value.length <= 253 && value.endsWith('.'));
|
|
144
|
+
const labels = value.slice(0, -1).split('.');
|
|
145
|
+
requireValue(labels.length >= 2 && labels.every(part => part.length <= 63
|
|
146
|
+
&& /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(part))
|
|
147
|
+
&& !['internal', 'localhost', 'local', 'arpa'].includes(labels[labels.length - 1]));
|
|
148
|
+
return value;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/** Structural validation only; backend IP ownership/reachability and policy fences are required. */
|
|
152
|
+
export const snapshotPrivateNetworkPolicyV1 = (value: unknown): IPrivateNetworkPolicyV1 => safe(() => {
|
|
153
|
+
const policy = object(value, ['connectivity', 'externalDns']);
|
|
154
|
+
requireValue(policy.connectivity === 'member-full-mesh');
|
|
155
|
+
if (policy.externalDns === null) return { connectivity: 'member-full-mesh', externalDns: null };
|
|
156
|
+
const external = object(policy.externalDns, ['upstreams', 'allowAnyPublicName', 'permittedSuffixes']);
|
|
157
|
+
const upstreams = array(external.upstreams, privateNetworkV1Contract.maximumUpstreams, entry => {
|
|
158
|
+
const upstream = object(entry, ['address', 'port']);
|
|
159
|
+
const port = counter(upstream.port);
|
|
160
|
+
requireValue(port <= 65535);
|
|
161
|
+
return { address: ipv4(upstream.address), port };
|
|
162
|
+
});
|
|
163
|
+
requireValue(upstreams.length > 0 && new Set(upstreams.map(upstream => `${upstream.address}:${upstream.port}`)).size === upstreams.length);
|
|
164
|
+
requireValue(typeof external.allowAnyPublicName === 'boolean');
|
|
165
|
+
const permittedSuffixes = sorted(array(external.permittedSuffixes, privateNetworkV1Contract.maximumPermittedSuffixes, publicSuffix));
|
|
166
|
+
requireValue(external.allowAnyPublicName ? permittedSuffixes.length === 0 : permittedSuffixes.length > 0);
|
|
167
|
+
return { connectivity: 'member-full-mesh', externalDns: {
|
|
168
|
+
upstreams, allowAnyPublicName: external.allowAnyPublicName, permittedSuffixes,
|
|
169
|
+
} };
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const membership = (attachmentsValue: unknown, defaultValue: unknown): Pick<IServicePrivateNetworksV1, 'attachments' | 'defaultNetworkId'> => {
|
|
173
|
+
const attachments = array(attachmentsValue, privateNetworkV1Contract.maximumAttachments, entry => {
|
|
174
|
+
const attachment = object(entry, ['networkId', 'aliases']);
|
|
175
|
+
return { networkId: identifier(attachment.networkId),
|
|
176
|
+
aliases: sorted(array(attachment.aliases, privateNetworkV1Contract.maximumAliasesPerAttachment, label)) };
|
|
177
|
+
});
|
|
178
|
+
sorted(attachments.map(attachment => attachment.networkId));
|
|
179
|
+
const defaultNetworkId = defaultValue === null ? null : identifier(defaultValue);
|
|
180
|
+
requireValue(defaultNetworkId === null || attachments.some(attachment => attachment.networkId === defaultNetworkId));
|
|
181
|
+
requireValue(attachments.length !== 1 || defaultNetworkId === attachments[0].networkId);
|
|
182
|
+
return { attachments, defaultNetworkId };
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export const snapshotPrivateNetworkV1 = (value: unknown): IPrivateNetworkV1 => safe(() => {
|
|
186
|
+
const network = object(value, ['schemaVersion', 'id', 'organizationId', 'organizationDnsKey', 'networkDnsKey',
|
|
187
|
+
'name', 'revision', 'state', 'policy', 'createdAt', 'updatedAt']);
|
|
188
|
+
requireValue(network.schemaVersion === 1 && ['active', 'retiring', 'retired'].includes(network.state as string));
|
|
189
|
+
const createdAt = counter(network.createdAt);
|
|
190
|
+
const updatedAt = counter(network.updatedAt);
|
|
191
|
+
requireValue(updatedAt >= createdAt);
|
|
192
|
+
return { schemaVersion: 1, id: identifier(network.id), organizationId: identifier(network.organizationId),
|
|
193
|
+
organizationDnsKey: dnsKey(network.organizationDnsKey, 'o'), networkDnsKey: dnsKey(network.networkDnsKey, 'n'),
|
|
194
|
+
name: name(network.name), revision: counter(network.revision), state: network.state as IPrivateNetworkV1['state'],
|
|
195
|
+
policy: snapshotPrivateNetworkPolicyV1(network.policy), createdAt, updatedAt };
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
export const snapshotServicePrivateNetworksV1 = (value: unknown): IServicePrivateNetworksV1 => safe(() => {
|
|
199
|
+
const service = object(value, ['schemaVersion', 'organizationId', 'serviceId', 'revision', 'attachments', 'defaultNetworkId']);
|
|
200
|
+
requireValue(service.schemaVersion === 1);
|
|
201
|
+
return { schemaVersion: 1, organizationId: identifier(service.organizationId), serviceId: identifier(service.serviceId),
|
|
202
|
+
revision: counter(service.revision), ...membership(service.attachments, service.defaultNetworkId) };
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
export const snapshotSetServicePrivateNetworksV1 = (value: unknown): ISetServicePrivateNetworksV1 => safe(() => {
|
|
206
|
+
const request = object(value, ['schemaVersion', 'organizationId', 'serviceId', 'mutationId', 'expectedRevision', 'attachments', 'defaultNetworkId']);
|
|
207
|
+
requireValue(request.schemaVersion === 1);
|
|
208
|
+
return { schemaVersion: 1, organizationId: identifier(request.organizationId), serviceId: identifier(request.serviceId),
|
|
209
|
+
mutationId: identifier(request.mutationId), expectedRevision: request.expectedRevision === null ? null : counter(request.expectedRevision),
|
|
210
|
+
...membership(request.attachments, request.defaultNetworkId) };
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
export const snapshotPrivateNetworkMutationV1 = (value: unknown): TPrivateNetworkMutationV1 => safe(() => {
|
|
214
|
+
requireValue(value !== null && typeof value === 'object');
|
|
215
|
+
const operation = Object.getOwnPropertyDescriptor(value, 'operation')?.value;
|
|
216
|
+
requireValue(operation === 'create' || operation === 'update' || operation === 'retire');
|
|
217
|
+
const request = object(value, ['schemaVersion', 'organizationId', 'mutationId', 'operation', 'expectedRevision',
|
|
218
|
+
...(operation === 'create' ? [] : ['networkId']), ...(operation === 'retire' ? [] : ['name', 'policy'])]);
|
|
219
|
+
requireValue(request.schemaVersion === 1);
|
|
220
|
+
const base = { schemaVersion: 1 as const, organizationId: identifier(request.organizationId), mutationId: identifier(request.mutationId) };
|
|
221
|
+
if (operation === 'create') {
|
|
222
|
+
requireValue(request.expectedRevision === null);
|
|
223
|
+
return { ...base, operation, expectedRevision: null, name: name(request.name), policy: snapshotPrivateNetworkPolicyV1(request.policy) };
|
|
224
|
+
}
|
|
225
|
+
const current = { networkId: identifier(request.networkId), expectedRevision: counter(request.expectedRevision) };
|
|
226
|
+
if (operation === 'retire') return { ...base, ...current, operation };
|
|
227
|
+
return { ...base, ...current, operation, name: name(request.name), policy: snapshotPrivateNetworkPolicyV1(request.policy) };
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
/** Pure naming helper; it does not reserve a name or create any public DNS/ingress record. */
|
|
231
|
+
export const buildPrivateNetworkFqdnV1 = (networkValue: unknown, aliasValue: unknown): string => safe(() => {
|
|
232
|
+
const network = snapshotPrivateNetworkV1(networkValue);
|
|
233
|
+
return `${label(aliasValue)}.${network.networkDnsKey}.${network.organizationDnsKey}.${privateNetworkV1Contract.privateRoot}`;
|
|
234
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/** Workload permissions are independent of human membership and deployment grants. */
|
|
2
|
+
export type TServiceMachineCapabilityV1 = 'platform:session';
|
|
3
|
+
|
|
4
|
+
/** Exact existing-service authority. Never infer this grant from an API role. */
|
|
5
|
+
export interface IServiceMachineGrantV1 {
|
|
6
|
+
organizationId: string;
|
|
7
|
+
serviceId: string;
|
|
8
|
+
capabilities: TServiceMachineCapabilityV1[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Public metadata only. The bearer is delivered through the owning service's SecretSet. */
|
|
12
|
+
export interface IServiceMachineCredentialMetadataV1 {
|
|
13
|
+
schemaVersion: 1;
|
|
14
|
+
credentialId: string;
|
|
15
|
+
userId: string;
|
|
16
|
+
grant: IServiceMachineGrantV1;
|
|
17
|
+
generation: number;
|
|
18
|
+
state: 'active' | 'revoked';
|
|
19
|
+
expiresAt: number;
|
|
20
|
+
createdAt: number;
|
|
21
|
+
updatedAt: number;
|
|
22
|
+
/** Exact immutable encrypted delivery version; null after revocation. */
|
|
23
|
+
delivery: { secretId: string; secretVersionId: string } | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface IServiceMachineCredentialMutationBaseV1 {
|
|
27
|
+
schemaVersion: 1;
|
|
28
|
+
organizationId: string;
|
|
29
|
+
serviceId: string;
|
|
30
|
+
/** Retain the complete request and this ID across retries. */
|
|
31
|
+
mutationId: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Creation is absent-only; rotation and revocation require the exact current generation. */
|
|
35
|
+
export type TServiceMachineCredentialMutationV1 = IServiceMachineCredentialMutationBaseV1 & (
|
|
36
|
+
| {
|
|
37
|
+
operation: 'ensure';
|
|
38
|
+
expectedGeneration: null;
|
|
39
|
+
capabilities: TServiceMachineCapabilityV1[];
|
|
40
|
+
expiresAt: number;
|
|
41
|
+
}
|
|
42
|
+
| {
|
|
43
|
+
operation: 'rotate';
|
|
44
|
+
expectedGeneration: number;
|
|
45
|
+
capabilities: TServiceMachineCapabilityV1[];
|
|
46
|
+
expiresAt: number;
|
|
47
|
+
}
|
|
48
|
+
| { operation: 'revoke'; expectedGeneration: number }
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
export interface IServiceMachineCredentialMutationResultV1 {
|
|
52
|
+
/** Historical on replay; replay never restores revoked or superseded authority. */
|
|
53
|
+
credential: IServiceMachineCredentialMetadataV1;
|
|
54
|
+
replayed: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const fail = (): never => { throw new Error('Service credential contract rejected.'); };
|
|
58
|
+
function requireValue(value: unknown): asserts value { if (!value) fail(); }
|
|
59
|
+
const object = (value: unknown, keys: string[]): Record<string, unknown> => {
|
|
60
|
+
requireValue(value !== null && typeof value === 'object' && !Array.isArray(value));
|
|
61
|
+
requireValue(Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
62
|
+
const actual = Reflect.ownKeys(value);
|
|
63
|
+
requireValue(actual.length === keys.length && keys.every((key) => actual.includes(key)));
|
|
64
|
+
const copy: Record<string, unknown> = Object.create(null);
|
|
65
|
+
for (const key of keys) {
|
|
66
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
67
|
+
requireValue(descriptor && Object.hasOwn(descriptor, 'value') && descriptor.enumerable);
|
|
68
|
+
copy[key] = descriptor.value;
|
|
69
|
+
}
|
|
70
|
+
return copy;
|
|
71
|
+
};
|
|
72
|
+
const identifier = (value: unknown): string => {
|
|
73
|
+
requireValue(typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/.test(value));
|
|
74
|
+
return value;
|
|
75
|
+
};
|
|
76
|
+
const counter = (value: unknown): number => {
|
|
77
|
+
requireValue(typeof value === 'number' && Number.isSafeInteger(value) && value > 0 && value < Number.MAX_SAFE_INTEGER);
|
|
78
|
+
return value;
|
|
79
|
+
};
|
|
80
|
+
const capabilities = (value: unknown): TServiceMachineCapabilityV1[] => {
|
|
81
|
+
requireValue(Array.isArray(value) && Object.getPrototypeOf(value) === Array.prototype);
|
|
82
|
+
const keys = Reflect.ownKeys(value);
|
|
83
|
+
requireValue(keys.length === 2 && keys.includes('length') && keys.includes('0'));
|
|
84
|
+
const entry = Object.getOwnPropertyDescriptor(value, '0');
|
|
85
|
+
requireValue(entry && Object.hasOwn(entry, 'value') && entry.enumerable && entry.value === 'platform:session');
|
|
86
|
+
return ['platform:session'];
|
|
87
|
+
};
|
|
88
|
+
const safe = <T>(operation: () => T): T => { try { return operation(); } catch { return fail(); } };
|
|
89
|
+
|
|
90
|
+
/** Structural copy only; the backend must fence live service ownership and credential authority. */
|
|
91
|
+
export const snapshotServiceMachineGrantV1 = (value: unknown): IServiceMachineGrantV1 => safe(() => {
|
|
92
|
+
const grant = object(value, ['organizationId', 'serviceId', 'capabilities']);
|
|
93
|
+
return { organizationId: identifier(grant.organizationId), serviceId: identifier(grant.serviceId),
|
|
94
|
+
capabilities: capabilities(grant.capabilities) };
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export const snapshotServiceMachineCredentialMutationV1 = (value: unknown): TServiceMachineCredentialMutationV1 => safe(() => {
|
|
98
|
+
requireValue(value !== null && typeof value === 'object');
|
|
99
|
+
const operation = Object.getOwnPropertyDescriptor(value, 'operation')?.value;
|
|
100
|
+
requireValue(operation === 'ensure' || operation === 'rotate' || operation === 'revoke');
|
|
101
|
+
const request = object(value, ['schemaVersion', 'organizationId', 'serviceId', 'mutationId', 'operation',
|
|
102
|
+
'expectedGeneration', ...(operation === 'revoke' ? [] : ['capabilities', 'expiresAt'])]);
|
|
103
|
+
requireValue(request.schemaVersion === 1);
|
|
104
|
+
const base = { schemaVersion: 1 as const, organizationId: identifier(request.organizationId),
|
|
105
|
+
serviceId: identifier(request.serviceId), mutationId: identifier(request.mutationId) };
|
|
106
|
+
if (operation === 'revoke') return { ...base, operation, expectedGeneration: counter(request.expectedGeneration) };
|
|
107
|
+
const material = { capabilities: capabilities(request.capabilities), expiresAt: counter(request.expiresAt) };
|
|
108
|
+
if (operation === 'ensure') {
|
|
109
|
+
requireValue(request.expectedGeneration === null);
|
|
110
|
+
return { ...base, ...material, operation, expectedGeneration: null };
|
|
111
|
+
}
|
|
112
|
+
return { ...base, ...material, operation, expectedGeneration: counter(request.expectedGeneration) };
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
export const snapshotServiceMachineCredentialMetadataV1 = (value: unknown): IServiceMachineCredentialMetadataV1 => safe(() => {
|
|
116
|
+
const metadata = object(value, ['schemaVersion', 'credentialId', 'userId', 'grant', 'generation', 'state',
|
|
117
|
+
'expiresAt', 'createdAt', 'updatedAt', 'delivery']);
|
|
118
|
+
requireValue(metadata.schemaVersion === 1 && (metadata.state === 'active' || metadata.state === 'revoked'));
|
|
119
|
+
const createdAt = counter(metadata.createdAt);
|
|
120
|
+
const updatedAt = counter(metadata.updatedAt);
|
|
121
|
+
requireValue(updatedAt >= createdAt);
|
|
122
|
+
let delivery: IServiceMachineCredentialMetadataV1['delivery'] = null;
|
|
123
|
+
if (metadata.state === 'active') {
|
|
124
|
+
const version = object(metadata.delivery, ['secretId', 'secretVersionId']);
|
|
125
|
+
delivery = { secretId: identifier(version.secretId), secretVersionId: identifier(version.secretVersionId) };
|
|
126
|
+
} else requireValue(metadata.delivery === null);
|
|
127
|
+
return { schemaVersion: 1, credentialId: identifier(metadata.credentialId), userId: identifier(metadata.userId),
|
|
128
|
+
grant: snapshotServiceMachineGrantV1(metadata.grant), generation: counter(metadata.generation),
|
|
129
|
+
state: metadata.state, expiresAt: counter(metadata.expiresAt), createdAt, updatedAt, delivery };
|
|
130
|
+
});
|
package/ts/requests/admin.ts
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
2
|
import type { IIdentity, IIdentityCredential, IServiceDeploymentGrant } from '../data/user.js';
|
|
3
|
+
import type {
|
|
4
|
+
IServiceMachineCredentialMetadataV1, IServiceMachineCredentialMutationResultV1,
|
|
5
|
+
TServiceMachineCredentialMutationV1,
|
|
6
|
+
} from '../data/servicecredential.js';
|
|
7
|
+
|
|
8
|
+
export interface IReq_Admin_GetServiceMachineCredentialV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
9
|
+
plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_GetServiceMachineCredentialV1
|
|
10
|
+
> {
|
|
11
|
+
method: 'getServiceMachineCredentialV1';
|
|
12
|
+
request: { identity: IIdentityCredential; organizationId: string; serviceId: string };
|
|
13
|
+
response: { credential: IServiceMachineCredentialMetadataV1 | null };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The backend authorizes the exact service and mutates hashed credential authority,
|
|
18
|
+
* encrypted SecretSet delivery and the replay receipt in one fenced transaction.
|
|
19
|
+
* No bearer is returned. Existing legacy deployment/cluster credentials are separate.
|
|
20
|
+
*/
|
|
21
|
+
export interface IReq_Admin_MutateServiceMachineCredentialV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
22
|
+
plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_MutateServiceMachineCredentialV1
|
|
23
|
+
> {
|
|
24
|
+
method: 'mutateServiceMachineCredentialV1';
|
|
25
|
+
request: { identity: IIdentityCredential; mutation: TServiceMachineCredentialMutationV1 };
|
|
26
|
+
response: IServiceMachineCredentialMutationResultV1;
|
|
27
|
+
}
|
|
3
28
|
|
|
4
29
|
/** Backend-owned human credential metadata; never includes a password verifier. */
|
|
5
30
|
export interface IHumanCredentialMetadata {
|
package/ts/requests/network.ts
CHANGED
|
@@ -1,4 +1,52 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
|
+
import type { IIdentityCredential } from '../data/user.js';
|
|
3
|
+
import type {
|
|
4
|
+
IPrivateNetworkV1, IServicePrivateNetworksV1, ISetServicePrivateNetworksV1,
|
|
5
|
+
TPrivateNetworkMutationV1,
|
|
6
|
+
} from '../data/privatenetwork.js';
|
|
7
|
+
|
|
8
|
+
/** Every request is authorized against live canonical membership and Cloudly policy. */
|
|
9
|
+
export interface IReq_Cloudly_ListPrivateNetworksV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
10
|
+
plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_ListPrivateNetworksV1
|
|
11
|
+
> {
|
|
12
|
+
method: 'listPrivateNetworksV1';
|
|
13
|
+
request: { identity: IIdentityCredential; organizationId: string; cursor: string | null; limit: number };
|
|
14
|
+
response: { networks: IPrivateNetworkV1[]; nextCursor: string | null };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface IReq_Cloudly_GetPrivateNetworkV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
18
|
+
plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_GetPrivateNetworkV1
|
|
19
|
+
> {
|
|
20
|
+
method: 'getPrivateNetworkV1';
|
|
21
|
+
request: { identity: IIdentityCredential; organizationId: string; networkId: string };
|
|
22
|
+
response: { network: IPrivateNetworkV1 | null };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface IReq_Cloudly_MutatePrivateNetworkV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
26
|
+
plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_MutatePrivateNetworkV1
|
|
27
|
+
> {
|
|
28
|
+
method: 'mutatePrivateNetworkV1';
|
|
29
|
+
request: { identity: IIdentityCredential; mutation: TPrivateNetworkMutationV1 };
|
|
30
|
+
/** Historical on replay; neither acceptance nor replay proves applied packet/DNS state. */
|
|
31
|
+
response: { network: IPrivateNetworkV1; replayed: boolean };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface IReq_Cloudly_GetServicePrivateNetworksV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
35
|
+
plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_GetServicePrivateNetworksV1
|
|
36
|
+
> {
|
|
37
|
+
method: 'getServicePrivateNetworksV1';
|
|
38
|
+
request: { identity: IIdentityCredential; organizationId: string; serviceId: string };
|
|
39
|
+
response: { membership: IServicePrivateNetworksV1 | null };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface IReq_Cloudly_SetServicePrivateNetworksV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
43
|
+
plugins.typedrequestInterfaces.ITypedRequest, IReq_Cloudly_SetServicePrivateNetworksV1
|
|
44
|
+
> {
|
|
45
|
+
method: 'setServicePrivateNetworksV1';
|
|
46
|
+
request: { identity: IIdentityCredential; mutation: ISetServicePrivateNetworksV1 };
|
|
47
|
+
/** Exact desired membership; enforcement and outstanding revocations remain separately observed. */
|
|
48
|
+
response: { membership: IServicePrivateNetworksV1; replayed: boolean };
|
|
49
|
+
}
|
|
2
50
|
|
|
3
51
|
export interface IRequest_Any_Cloudly_GetNetworkNodes {
|
|
4
52
|
method: 'getNetworkNodes';
|