@serve.zone/interfaces 32.28.0 → 32.30.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.
Files changed (39) hide show
  1. package/changelog.md +81 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/data/cluster.d.ts +1 -1
  4. package/dist_ts/data/cluster.js +6 -1
  5. package/dist_ts/data/networkaddressplan.d.ts +27 -3
  6. package/dist_ts/data/networkaddressplan.js +38 -2
  7. package/dist_ts/data/networkprojection.d.ts +74 -3
  8. package/dist_ts/data/networkprojection.js +21 -2
  9. package/dist_ts/data/secret.d.ts +11 -0
  10. package/dist_ts/data/secret.js +13 -1
  11. package/dist_ts/data/serviceruntime.d.ts +19 -3
  12. package/dist_ts/data/serviceruntime.js +2 -2
  13. package/dist_ts/data/settings.d.ts +51 -0
  14. package/dist_ts/data/settings.js +83 -1
  15. package/dist_ts/platform/corestoreendpointselection.d.ts +37 -0
  16. package/dist_ts/platform/corestoreendpointselection.js +45 -0
  17. package/dist_ts/platform/index.d.ts +3 -1
  18. package/dist_ts/platform/index.js +4 -2
  19. package/dist_ts/platform/objectstorage.js +5 -1
  20. package/dist_ts/private/networkcontract.d.ts +20 -0
  21. package/dist_ts/private/networkcontract.js +31 -9
  22. package/dist_ts/requests/network.d.ts +62 -3
  23. package/dist_ts/requests/settings.d.ts +56 -2
  24. package/dist_ts/requests/settings.js +91 -1
  25. package/package.json +3 -3
  26. package/readme.md +151 -12
  27. package/ts/00_commitinfo_data.ts +1 -1
  28. package/ts/data/cluster.ts +5 -0
  29. package/ts/data/networkaddressplan.ts +66 -4
  30. package/ts/data/networkprojection.ts +96 -4
  31. package/ts/data/secret.ts +16 -0
  32. package/ts/data/serviceruntime.ts +19 -3
  33. package/ts/data/settings.ts +127 -0
  34. package/ts/platform/corestoreendpointselection.ts +79 -0
  35. package/ts/platform/index.ts +3 -0
  36. package/ts/platform/objectstorage.ts +4 -0
  37. package/ts/private/networkcontract.ts +43 -4
  38. package/ts/requests/network.ts +58 -3
  39. package/ts/requests/settings.ts +162 -2
@@ -1,6 +1,10 @@
1
1
  import * as plugins from '../plugins.js';
2
- import type { ICloudlySettings } from '../data/settings.js';
3
- import type { IIdentity } from '../data/user.js';
2
+ import * as secretData from '../data/secret.js';
3
+ import * as settingsData from '../data/settings.js';
4
+ import type { IActiveSecretRecipientMetadata } from '../data/secret.js';
5
+ import type { ICloudlySettings, IExternalGatewayEnrollmentState } from '../data/settings.js';
6
+ import type { IIdentity, IIdentityCredential } from '../data/user.js';
7
+ import { readDataProperties, hasExactKeys } from '../private/dataproperties.js';
4
8
 
5
9
  // Get Settings
6
10
  export interface IRequest_GetSettings extends plugins.typedrequestInterfaces.ITypedRequest {
@@ -64,3 +68,159 @@ export interface IRequest_GetSetting extends plugins.typedrequestInterfaces.ITyp
64
68
  value: ICloudlySettings[keyof ICloudlySettings] | undefined;
65
69
  };
66
70
  }
71
+
72
+ /** A dcrouter admin bootstrap token, sealed to Cloudly's active secret ingress recipient. */
73
+ export interface IExternalGatewayBootstrapTokenInput {
74
+ mode: 'sealed';
75
+ /** Sealed under `data.createGatewayBootstrapEnvelopeContext` for the request's expected target. */
76
+ envelope: plugins.smartcrypto.IX25519EnvelopeV1;
77
+ }
78
+
79
+ /**
80
+ * Enrolls Cloudly as its dcrouter gateway client with a dcrouter admin token an administrator
81
+ * hands in once.
82
+ *
83
+ * Cloudly opens the token, checks the target the request names against its own settings, asks
84
+ * dcrouter whether the token is an admin token, and provisions, finalizes and stores its own
85
+ * least-privilege gateway-client credential with it — under the policy its settings and the runtime
86
+ * network address plan state now. The admin token is used for this one call and never stored; the
87
+ * administrator revokes it at dcrouter afterwards. A refusal leaves the credential Cloudly already
88
+ * holds unchanged and is answered with one of `data.externalGatewayBootstrapRefusals`.
89
+ *
90
+ * This is the one way a gateway credential enters Cloudly, for a first enrollment and for the
91
+ * re-bootstrap a changed address plan or gateway setting asks for (`admin-bootstrap-required`).
92
+ */
93
+ export interface IReq_Any_Cloudly_BootstrapExternalGatewayCredential
94
+ extends plugins.typedrequestInterfaces.implementsTR<
95
+ plugins.typedrequestInterfaces.ITypedRequest,
96
+ IReq_Any_Cloudly_BootstrapExternalGatewayCredential
97
+ > {
98
+ method: 'bootstrapExternalGatewayCredential';
99
+ request: {
100
+ identity: IIdentityCredential;
101
+ /** The `dcrouterGatewayUrl` the administrator read from Cloudly's settings. */
102
+ expectedGatewayUrl: string;
103
+ /** The `dcrouterGatewayClientId` the administrator read from Cloudly's settings. */
104
+ expectedGatewayClientId: string;
105
+ bootstrapToken: IExternalGatewayBootstrapTokenInput;
106
+ };
107
+ response: {
108
+ /** The enrollment after the bootstrap: `ready` when the new credential is Cloudly's runtime authority. */
109
+ state: IExternalGatewayEnrollmentState;
110
+ };
111
+ }
112
+
113
+ /** Cloudly's gateway-client enrollment state, for an administrator; it never carries a credential. */
114
+ export interface IReq_Any_Cloudly_GetExternalGatewayEnrollmentState
115
+ extends plugins.typedrequestInterfaces.implementsTR<
116
+ plugins.typedrequestInterfaces.ITypedRequest,
117
+ IReq_Any_Cloudly_GetExternalGatewayEnrollmentState
118
+ > {
119
+ method: 'getExternalGatewayEnrollmentState';
120
+ request: {
121
+ identity: IIdentityCredential;
122
+ };
123
+ response: {
124
+ state: IExternalGatewayEnrollmentState;
125
+ };
126
+ }
127
+
128
+ const hasJwtIdentity = (valueArg: unknown): boolean => {
129
+ const identity = readDataProperties(valueArg);
130
+ return identity !== null && typeof identity.jwt === 'string' && identity.jwt.length > 0;
131
+ };
132
+
133
+ /** Structural check of the target a bootstrap request names; empty when both are well formed. */
134
+ const validateExpectedGatewayTarget = (urlArg: unknown, clientIdArg: unknown): string[] => {
135
+ const errors: string[] = [];
136
+ let url: URL | undefined;
137
+ if (typeof urlArg === 'string'
138
+ && urlArg.length > 0
139
+ && urlArg.length <= settingsData.externalGatewayBootstrapContract.maximumGatewayUrlLength) {
140
+ try {
141
+ url = new URL(urlArg);
142
+ } catch {
143
+ url = undefined;
144
+ }
145
+ }
146
+ if (!url
147
+ || url.protocol !== 'https:'
148
+ || url.username !== ''
149
+ || url.password !== ''
150
+ || url.search !== ''
151
+ || url.hash !== '') {
152
+ errors.push('bootstrap expectedGatewayUrl must be an HTTPS URL without credentials, query or fragment');
153
+ }
154
+ if (typeof clientIdArg !== 'string'
155
+ || clientIdArg.length === 0
156
+ || clientIdArg.length > settingsData.externalGatewayBootstrapContract.maximumGatewayClientIdLength
157
+ || !/^[\x21-\x7e]+$/.test(clientIdArg)) {
158
+ errors.push('bootstrap expectedGatewayClientId must be a bounded identifier without whitespace');
159
+ }
160
+ return errors;
161
+ };
162
+
163
+ /**
164
+ * Checks a bootstrap request against its schema and the active ingress recipient, without opening
165
+ * the envelope: the recipient, the context digest of the named target and the ciphertext bound of
166
+ * `data.externalGatewayBootstrapContract.maximumTokenBytes`. Every problem is named.
167
+ */
168
+ export const validateBootstrapExternalGatewayCredentialRequest = async (
169
+ requestArg: unknown,
170
+ activeIngressRecipientArg: IActiveSecretRecipientMetadata,
171
+ ): Promise<string[]> => {
172
+ const request = readDataProperties(requestArg);
173
+ if (request === null) return ['bootstrap request must be a plain record'];
174
+ const errors: string[] = [];
175
+ if (!hasExactKeys(request, ['identity', 'expectedGatewayUrl', 'expectedGatewayClientId', 'bootstrapToken'])) {
176
+ errors.push('bootstrap request contains fields outside its schema');
177
+ }
178
+ if (!hasJwtIdentity(request.identity)) {
179
+ errors.push('bootstrap identity must be a JWT credential');
180
+ }
181
+ const targetErrors = validateExpectedGatewayTarget(
182
+ request.expectedGatewayUrl,
183
+ request.expectedGatewayClientId,
184
+ );
185
+ errors.push(...targetErrors);
186
+ const tokenInput = readDataProperties(request.bootstrapToken);
187
+ if (tokenInput === null || tokenInput.mode !== 'sealed') {
188
+ errors.push('bootstrap token must be a sealed input');
189
+ return errors;
190
+ }
191
+ const expectedContext = targetErrors.length === 0
192
+ ? secretData.createGatewayBootstrapEnvelopeContext({
193
+ gatewayUrl: request.expectedGatewayUrl as string,
194
+ gatewayClientId: request.expectedGatewayClientId as string,
195
+ })
196
+ : new Uint8Array();
197
+ errors.push(...await secretData.validateSecretValueInputForRecipient(
198
+ tokenInput,
199
+ activeIngressRecipientArg,
200
+ expectedContext,
201
+ ));
202
+ const envelope = readDataProperties(tokenInput.envelope);
203
+ if (envelope !== null
204
+ && typeof envelope.ciphertext === 'string'
205
+ && envelope.ciphertext.length
206
+ > Math.ceil(settingsData.externalGatewayBootstrapContract.maximumTokenBytes * 4 / 3)) {
207
+ errors.push(
208
+ `bootstrap token must not exceed ${settingsData.externalGatewayBootstrapContract.maximumTokenBytes} bytes`,
209
+ );
210
+ }
211
+ return errors;
212
+ };
213
+
214
+ /** Checks an enrollment state read; every problem is named. */
215
+ export const validateGetExternalGatewayEnrollmentStateRequest = (requestArg: unknown): string[] => {
216
+ const request = readDataProperties(requestArg);
217
+ if (request === null) return ['enrollment state request must be a plain record'];
218
+ const errors: string[] = [];
219
+ if (!hasExactKeys(request, ['identity'])) {
220
+ errors.push('enrollment state request contains fields outside its schema');
221
+ }
222
+ if (!hasJwtIdentity(request.identity)) {
223
+ errors.push('enrollment state identity must be a JWT credential');
224
+ }
225
+ return errors;
226
+ };