@serve.zone/interfaces 32.59.0 → 32.61.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/.smartconfig.json +4 -2
- package/changelog.md +87 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/delegatedadmintoken.d.ts +142 -0
- package/dist_ts/data/delegatedadmintoken.js +197 -0
- package/dist_ts/data/externalregistry.d.ts +37 -1
- package/dist_ts/data/gateway.d.ts +23 -4
- package/dist_ts/data/gateway.js +1 -1
- package/dist_ts/data/index.d.ts +2 -0
- package/dist_ts/data/index.js +3 -1
- package/dist_ts/data/launchersecretowner.d.ts +60 -0
- package/dist_ts/data/launchersecretowner.js +94 -0
- package/dist_ts/data/secret.d.ts +67 -1
- package/dist_ts/data/secret.js +59 -6
- package/dist_ts/data/serviceruntime.d.ts +9 -2
- package/dist_ts/data/serviceruntime.js +2 -1
- package/dist_ts/platform/objectstorageenvironment.d.ts +12 -3
- package/dist_ts/platform/objectstorageenvironment.js +22 -6
- package/dist_ts/requests/admin.d.ts +52 -0
- package/dist_ts/requests/externalregistry.d.ts +12 -6
- package/dist_ts/requests/identity.d.ts +4 -0
- package/dist_ts/requests/service.d.ts +16 -1
- package/package.json +5 -8
- package/readme.md +76 -12
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/delegatedadmintoken.ts +282 -0
- package/ts/data/externalregistry.ts +60 -2
- package/ts/data/gateway.ts +24 -3
- package/ts/data/index.ts +2 -0
- package/ts/data/launchersecretowner.ts +145 -0
- package/ts/data/secret.ts +103 -7
- package/ts/data/serviceruntime.ts +9 -1
- package/ts/platform/objectstorageenvironment.ts +26 -8
- package/ts/requests/admin.ts +51 -0
- package/ts/requests/externalregistry.ts +12 -6
- package/ts/requests/identity.ts +4 -0
- package/ts/requests/service.ts +16 -1
package/ts/data/secret.ts
CHANGED
|
@@ -228,7 +228,33 @@ export interface ISecretSetConsumerRolloutStatus {
|
|
|
228
228
|
updatedAt: number;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
/**
|
|
232
|
+
* A launcher-environment delivery as a caller states it: on a Secret's `defaultDelivery`, a
|
|
233
|
+
* SecretSet attachment override, a create request or an App Store declaration. The owner of the
|
|
234
|
+
* file WorkloadInit reads is not the caller's to state; the resolver derives it.
|
|
235
|
+
*/
|
|
231
236
|
export interface ISecretLauncherEnvironmentDelivery {
|
|
237
|
+
type: 'launcher-environment';
|
|
238
|
+
variableName: string;
|
|
239
|
+
/**
|
|
240
|
+
* @deprecated Derived by the resolver from the workload identity
|
|
241
|
+
* (`deriveLauncherSecretDeliveryOwner`); ignored; removed in the next major.
|
|
242
|
+
*/
|
|
243
|
+
uid?: number;
|
|
244
|
+
/**
|
|
245
|
+
* @deprecated Derived by the resolver from the workload identity
|
|
246
|
+
* (`deriveLauncherSecretDeliveryOwner`); ignored; removed in the next major.
|
|
247
|
+
*/
|
|
248
|
+
gid?: number;
|
|
249
|
+
mode: 0o400 | 0o440;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* A launcher-environment delivery of a resolved secret manifest. `uid` and `gid` are the owner the
|
|
254
|
+
* resolver derived from the workload identity (`deriveLauncherSecretDeliveryOwner`), written by
|
|
255
|
+
* `createResolvedSecretDelivery`; the node mounts the file with them.
|
|
256
|
+
*/
|
|
257
|
+
export interface ISecretResolvedLauncherEnvironmentDelivery {
|
|
232
258
|
type: 'launcher-environment';
|
|
233
259
|
variableName: string;
|
|
234
260
|
uid: number;
|
|
@@ -244,8 +270,50 @@ export interface ISecretFileDelivery {
|
|
|
244
270
|
mode: 0o400 | 0o440;
|
|
245
271
|
}
|
|
246
272
|
|
|
273
|
+
/** A delivery as a caller states it. */
|
|
247
274
|
export type TSecretDelivery = ISecretLauncherEnvironmentDelivery | ISecretFileDelivery;
|
|
248
275
|
|
|
276
|
+
/** A delivery of a resolved secret manifest: every owner is stated. */
|
|
277
|
+
export type TSecretResolvedDelivery = ISecretResolvedLauncherEnvironmentDelivery | ISecretFileDelivery;
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Prefixes of the names the platform injects into a workload itself (`SERVEZONE_*`): a secret of
|
|
281
|
+
* such a name would replace what the platform delivers.
|
|
282
|
+
*/
|
|
283
|
+
export const platformReservedSecretKeyPrefixes = Object.freeze(['SERVEZONE_'] as const);
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Prefixes of the names a loader, language runtime, package manager or library reads to change
|
|
287
|
+
* what a process loads, runs or trusts (`LD_PRELOAD`, `NODE_OPTIONS`, `PYTHONPATH`, `OPENSSL_CONF`,
|
|
288
|
+
* `DOTNET_STARTUP_HOOKS`, `NPM_CONFIG_REGISTRY`, …). Matched as plain prefixes, so `PYTHON`, `PERL`,
|
|
289
|
+
* `RUBY` and `PHP` cover every name that starts with them.
|
|
290
|
+
*/
|
|
291
|
+
export const runtimeInjectionSecretKeyPrefixes = Object.freeze([
|
|
292
|
+
'LD_', 'DYLD_', 'NODE_', 'DENO_', 'BUN_', 'PYTHON', 'PERL', 'RUBY', 'PHP', 'JAVA_', 'JDK_', '_JAVA_', 'DOTNET_',
|
|
293
|
+
'CORECLR_', 'LUA_', 'GEM_', 'BUNDLE_', 'NPM_CONFIG_', 'PIP_', 'GLIBC_', 'MALLOC_', 'GCONV_', 'OPENSSL_', 'SSL_',
|
|
294
|
+
'CURL_', 'REQUESTS_', 'GIT_',
|
|
295
|
+
] as const);
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Exact names a shell, loader, runtime or HTTP client reads to change where a process looks,
|
|
299
|
+
* connects or what it trusts or records (`CLASSPATH`, `AWS_CA_BUNDLE`, `SSLKEYLOGFILE`).
|
|
300
|
+
*/
|
|
301
|
+
export const runtimeInjectionSecretKeys = Object.freeze([
|
|
302
|
+
'PATH', 'HOME', 'SHELL', 'ENV', 'BASH_ENV', 'IFS', 'TMPDIR', 'LOCPATH', 'NLSPATH', 'CLASSPATH', 'HTTP_PROXY',
|
|
303
|
+
'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY', 'FTP_PROXY', 'AWS_CA_BUNDLE', 'SSLKEYLOGFILE',
|
|
304
|
+
] as const);
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Whether a secret key, or a name a secret is delivered under, is reserved: one of
|
|
308
|
+
* `runtimeInjectionSecretKeys`, or starting with an entry of `platformReservedSecretKeyPrefixes`
|
|
309
|
+
* or `runtimeInjectionSecretKeyPrefixes`. A delegated administrator token never writes such a name.
|
|
310
|
+
*/
|
|
311
|
+
export const isReservedSecretKey = (keyArg: string): boolean => (
|
|
312
|
+
(runtimeInjectionSecretKeys as readonly string[]).includes(keyArg)
|
|
313
|
+
|| platformReservedSecretKeyPrefixes.some((prefixArg) => keyArg.startsWith(prefixArg))
|
|
314
|
+
|| runtimeInjectionSecretKeyPrefixes.some((prefixArg) => keyArg.startsWith(prefixArg))
|
|
315
|
+
);
|
|
316
|
+
|
|
249
317
|
export const secretValueLimits = Object.freeze({
|
|
250
318
|
maximumBytes: 500 * 1024,
|
|
251
319
|
minimumGeneratedBytes: 16,
|
|
@@ -299,7 +367,7 @@ export interface IResolvedSecretManifestEntry {
|
|
|
299
367
|
key: string;
|
|
300
368
|
environment: TSecretEnvironment;
|
|
301
369
|
owner: TWorkloadSecretOwner;
|
|
302
|
-
delivery:
|
|
370
|
+
delivery: TSecretResolvedDelivery;
|
|
303
371
|
}
|
|
304
372
|
|
|
305
373
|
/** Immutable, value-free runtime snapshot. */
|
|
@@ -709,14 +777,16 @@ export const validateSecretManagementScope = (
|
|
|
709
777
|
: ['management scope prefix must match management source'];
|
|
710
778
|
};
|
|
711
779
|
|
|
712
|
-
|
|
780
|
+
const validateDelivery = (deliveryArg: unknown, launcherOwnerArg: 'tolerated' | 'required'): string[] => {
|
|
713
781
|
if (!isRecord(deliveryArg)) return ['delivery must be an object'];
|
|
714
782
|
const isLauncher = deliveryArg.type === 'launcher-environment';
|
|
715
783
|
if (!isLauncher && deliveryArg.type !== 'file') {
|
|
716
784
|
return ['delivery type must be launcher-environment or file'];
|
|
717
785
|
}
|
|
786
|
+
const statesOwner = !isLauncher || launcherOwnerArg === 'required'
|
|
787
|
+
|| deliveryArg.uid !== undefined || deliveryArg.gid !== undefined;
|
|
718
788
|
const expectedKeys = isLauncher
|
|
719
|
-
? ['type', 'variableName', 'uid', 'gid', 'mode']
|
|
789
|
+
? ['type', 'variableName', ...(statesOwner ? ['uid', 'gid'] : []), 'mode']
|
|
720
790
|
: ['type', 'targetPath', 'uid', 'gid', 'mode'];
|
|
721
791
|
if (!hasExactKeys(deliveryArg, expectedKeys)) {
|
|
722
792
|
return ['delivery must use its exact schema'];
|
|
@@ -730,7 +800,7 @@ export const validateSecretDelivery = (deliveryArg: unknown): string[] => {
|
|
|
730
800
|
|| !/^\/run\/secrets\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(deliveryArg.targetPath))) {
|
|
731
801
|
errors.push('file delivery targetPath must be a canonical file below /run/secrets');
|
|
732
802
|
}
|
|
733
|
-
for (const field of ['uid', 'gid'] as const) {
|
|
803
|
+
for (const field of statesOwner ? ['uid', 'gid'] as const : []) {
|
|
734
804
|
const value = deliveryArg[field];
|
|
735
805
|
if (!Number.isSafeInteger(value)
|
|
736
806
|
|| (value as number) < 0
|
|
@@ -744,6 +814,30 @@ export const validateSecretDelivery = (deliveryArg: unknown): string[] => {
|
|
|
744
814
|
return errors;
|
|
745
815
|
};
|
|
746
816
|
|
|
817
|
+
/**
|
|
818
|
+
* Validates a delivery as a caller states it. A launcher delivery should state no owner; one that
|
|
819
|
+
* states the deprecated `uid` and `gid` (both, bounded) is tolerated until the next major.
|
|
820
|
+
*/
|
|
821
|
+
export const validateSecretDelivery = (deliveryArg: unknown): string[] => validateDelivery(deliveryArg, 'tolerated');
|
|
822
|
+
|
|
823
|
+
/** Validates a resolved manifest's delivery: a launcher delivery states its derived owner. */
|
|
824
|
+
export const validateSecretResolvedDelivery = (deliveryArg: unknown): string[] => validateDelivery(
|
|
825
|
+
deliveryArg, 'required');
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* The resolved manifest delivery of a caller's delivery: a launcher delivery takes the owner the
|
|
829
|
+
* resolver derived (`deriveLauncherSecretDeliveryOwner`) and drops any deprecated owner the caller
|
|
830
|
+
* stated; a file delivery keeps its own.
|
|
831
|
+
*/
|
|
832
|
+
export const createResolvedSecretDelivery = (
|
|
833
|
+
deliveryArg: TSecretDelivery,
|
|
834
|
+
ownerArg: { uid: number; gid: number },
|
|
835
|
+
): TSecretResolvedDelivery => (deliveryArg.type === 'launcher-environment'
|
|
836
|
+
? { type: 'launcher-environment', variableName: deliveryArg.variableName, uid: ownerArg.uid, gid: ownerArg.gid,
|
|
837
|
+
mode: deliveryArg.mode }
|
|
838
|
+
: { type: 'file', targetPath: deliveryArg.targetPath, uid: deliveryArg.uid, gid: deliveryArg.gid,
|
|
839
|
+
mode: deliveryArg.mode });
|
|
840
|
+
|
|
747
841
|
export const validateSecretMetadata = (secretArg: unknown): string[] => {
|
|
748
842
|
if (!isRecord(secretArg)) return ['secret metadata must be an object'];
|
|
749
843
|
const expectedKeys = [
|
|
@@ -1269,7 +1363,7 @@ export const validateResolvedSecretManifest = (
|
|
|
1269
1363
|
`secret manifest entries[${index}].owner serviceId must match the manifest serviceId`,
|
|
1270
1364
|
);
|
|
1271
1365
|
}
|
|
1272
|
-
errors.push(...
|
|
1366
|
+
errors.push(...validateSecretResolvedDelivery(candidate.delivery).map(
|
|
1273
1367
|
(errorArg) => `secret manifest entries[${index}].${errorArg}`,
|
|
1274
1368
|
));
|
|
1275
1369
|
if (isRecord(candidate.delivery)) {
|
|
@@ -1281,7 +1375,7 @@ export const validateResolvedSecretManifest = (
|
|
|
1281
1375
|
} else if (
|
|
1282
1376
|
candidate.delivery.type === 'file'
|
|
1283
1377
|
&& typeof candidate.delivery.targetPath === 'string'
|
|
1284
|
-
&&
|
|
1378
|
+
&& validateSecretResolvedDelivery(candidate.delivery).length === 0
|
|
1285
1379
|
) {
|
|
1286
1380
|
fileDeliveryTargets.push(candidate.delivery.targetPath);
|
|
1287
1381
|
}
|
|
@@ -1584,7 +1678,7 @@ export const createWorkloadInitEnvironmentMap = async (
|
|
|
1584
1678
|
.map(async (entryArg) => {
|
|
1585
1679
|
const resource = await createDockerSecretResourceName(entryArg.secretVersionId);
|
|
1586
1680
|
return {
|
|
1587
|
-
variableName: (entryArg.delivery as
|
|
1681
|
+
variableName: (entryArg.delivery as ISecretResolvedLauncherEnvironmentDelivery).variableName,
|
|
1588
1682
|
resource,
|
|
1589
1683
|
source: createLauncherSecretSourcePath(resource),
|
|
1590
1684
|
};
|
|
@@ -1818,6 +1912,8 @@ const canonicalSecretOwner = (ownerArg: TSecretOwner): Record<string, string> =>
|
|
|
1818
1912
|
}
|
|
1819
1913
|
};
|
|
1820
1914
|
|
|
1915
|
+
// A launcher delivery that still states the deprecated owner keeps the context it was sealed
|
|
1916
|
+
// under; JSON leaves an absent owner out.
|
|
1821
1917
|
const canonicalSecretDelivery = (deliveryArg: TSecretDelivery): Record<string, unknown> => (
|
|
1822
1918
|
deliveryArg.type === 'launcher-environment'
|
|
1823
1919
|
? {
|
|
@@ -89,6 +89,7 @@ const serviceRuntimeRefusals = [
|
|
|
89
89
|
'network-workload-pool-missing', 'network-workload-pool-exhausted',
|
|
90
90
|
'volume-driver-unsupported', 'volume-options-unsupported', 'volume-shared-unsupported',
|
|
91
91
|
'volume-placement-unsupported', 'volume-initialization-unknown', 'volume-ownership-unknown',
|
|
92
|
+
'launcher-secret-ownership-unknown',
|
|
92
93
|
'objectstorage-endpoint-missing', 'objectstorage-binding-invalid', 'objectstorage-environment-conflict',
|
|
93
94
|
'objectstorage-endpoint-unselected', 'database-endpoint-missing', 'database-endpoint-unselected',
|
|
94
95
|
'published-port-reserved', 'published-port-symmetric-without-egress', 'readiness-network-unsupported',
|
|
@@ -128,11 +129,18 @@ export type TServiceRuntimeWaitingReason = typeof serviceRuntimeWaitingReasons[n
|
|
|
128
129
|
* `volume-ownership-unknown` a `run-as` ownership under `runAs: null`
|
|
129
130
|
* (`deriveServiceVolumeClaimInitialization`); neither is defaulted.
|
|
130
131
|
*
|
|
132
|
+
* `launcher-secret-ownership-unknown` is a service with launcher-environment secrets whose runtime
|
|
133
|
+
* spec runs the image's own user (`runAs: null`) while that user's numeric owner is not known: a
|
|
134
|
+
* named non-root user, a numeric part above the identity bound, a group a `0o440` delivery would grant that the image does not state
|
|
135
|
+
* numerically, or platforms of the pinned index that derive different owners
|
|
136
|
+
* (`deriveLauncherSecretDeliveryOwner`). WorkloadInit could not read secret files owned by a guess.
|
|
137
|
+
*
|
|
131
138
|
* The object-storage refusals name why a binding's environment cannot be composed
|
|
132
139
|
* (`composeObjectStorageServiceEnvironment`): `objectstorage-endpoint-missing` a binding whose node
|
|
133
140
|
* states no valid `corestoreObjectStorage`, `objectstorage-binding-invalid` a binding
|
|
134
141
|
* `createObjectStorageBindingEnvironment` refuses, and `objectstorage-environment-conflict` a name
|
|
135
|
-
* the service's own environment or another binding already
|
|
142
|
+
* the service's own environment, one of its launcher-environment secrets or another binding already
|
|
143
|
+
* sets.
|
|
136
144
|
*
|
|
137
145
|
* `database-endpoint-missing` is an enabled database binding placed on a node that states no valid
|
|
138
146
|
* `corestoreDatabase`: its credential cannot be addressed to any host a workload reaches, so it is
|
|
@@ -201,10 +201,17 @@ const findEnvironmentCollisions = (
|
|
|
201
201
|
keysArg: readonly string[],
|
|
202
202
|
): string[] => keysArg.filter((keyArg) => claimedKeysArg.has(keyArg)).sort();
|
|
203
203
|
|
|
204
|
-
const describeEnvironmentCollisions = (
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
)
|
|
204
|
+
const describeEnvironmentCollisions = (
|
|
205
|
+
collisionsArg: readonly string[],
|
|
206
|
+
secretNamesArg: ReadonlySet<string> = new Set(),
|
|
207
|
+
): string => {
|
|
208
|
+
const secretCollisions = collisionsArg.filter((keyArg) => secretNamesArg.has(keyArg));
|
|
209
|
+
return `objectstorage binding environment collides with ${collisionsArg.join(', ')} already set on `
|
|
210
|
+
+ 'the workload' + (secretCollisions.length > 0
|
|
211
|
+
? `; ${secretCollisions.join(', ')} reaches it as a launcher-environment secret, which would `
|
|
212
|
+
+ 'replace the binding\'s value'
|
|
213
|
+
: '');
|
|
214
|
+
};
|
|
208
215
|
|
|
209
216
|
/**
|
|
210
217
|
* A workload environment with one binding's environment added.
|
|
@@ -252,8 +259,8 @@ export type TObjectStorageEnvironmentWaitingReason = Extract<
|
|
|
252
259
|
* `corestoreObjectStorage`; `objectstorage-binding-invalid` a binding
|
|
253
260
|
* `createObjectStorageBindingEnvironment()` refuses (not `objectstorage`, no canonical bucket, no
|
|
254
261
|
* canonical retention intent, or evidence that does not prove this binding);
|
|
255
|
-
* `objectstorage-environment-conflict` a name the service's own environment
|
|
256
|
-
* already sets.
|
|
262
|
+
* `objectstorage-environment-conflict` a name the service's own environment, one of its
|
|
263
|
+
* launcher-environment secrets or another binding already sets.
|
|
257
264
|
*/
|
|
258
265
|
export type TObjectStorageEnvironmentRefusal = Extract<
|
|
259
266
|
TServiceRuntimeRefusal,
|
|
@@ -285,16 +292,27 @@ export type TObjectStorageServiceEnvironmentComposition =
|
|
|
285
292
|
* already claims all twenty of its names, so a collision with it is refused now, not once its
|
|
286
293
|
* evidence arrives. The first refusal is answered, otherwise the first wait, otherwise the
|
|
287
294
|
* composed environment. The inputs are not changed.
|
|
295
|
+
*
|
|
296
|
+
* `secretEnvironmentNamesArg` names the variables the workload receives as launcher-environment
|
|
297
|
+
* secrets (each `ISecretResolvedLauncherEnvironmentDelivery.variableName` of its desired
|
|
298
|
+
* manifest), names only, never a value. The launcher sets a secret over a plain variable of the same name, so a
|
|
299
|
+
* secret named like one of a binding's names would silently replace the binding's value: such a
|
|
300
|
+
* name claims the binding's name exactly as the service's own environment does, and is refused as
|
|
301
|
+
* `objectstorage-environment-conflict`. A secret by any other name is not this composition's
|
|
302
|
+
* concern, and neither are a binding's credential names (`corestoreObjectStorageCredentialSecretKeys`),
|
|
303
|
+
* which the binding itself delivers as secrets and never as plain names.
|
|
288
304
|
*/
|
|
289
305
|
export const composeObjectStorageServiceEnvironment = async (
|
|
290
306
|
serviceEnvironmentArg: Readonly<Record<string, string>>,
|
|
291
307
|
sourcesArg: readonly IObjectStorageBindingEnvironmentSource[],
|
|
308
|
+
secretEnvironmentNamesArg: readonly string[] = [],
|
|
292
309
|
): Promise<TObjectStorageServiceEnvironmentComposition> => {
|
|
293
310
|
const sources = [...sourcesArg].sort((leftArg, rightArg) => (
|
|
294
311
|
leftArg.binding.id < rightArg.binding.id ? -1 : leftArg.binding.id > rightArg.binding.id ? 1 : 0
|
|
295
312
|
));
|
|
296
313
|
let environment: Record<string, string> = { ...serviceEnvironmentArg };
|
|
297
|
-
const
|
|
314
|
+
const secretNames: ReadonlySet<string> = new Set(secretEnvironmentNamesArg);
|
|
315
|
+
const claimedKeys = new Set([...Object.keys(environment), ...secretNames]);
|
|
298
316
|
let wait: Extract<TObjectStorageServiceEnvironmentComposition, { status: 'waiting' }> | null = null;
|
|
299
317
|
for (const source of sources) {
|
|
300
318
|
const bindingId = source.binding.id;
|
|
@@ -308,7 +326,7 @@ export const composeObjectStorageServiceEnvironment = async (
|
|
|
308
326
|
const collisions = findEnvironmentCollisions(claimedKeys, keys);
|
|
309
327
|
if (collisions.length > 0) {
|
|
310
328
|
return { status: 'refused', reason: 'objectstorage-environment-conflict', bindingId,
|
|
311
|
-
message: describeEnvironmentCollisions(collisions) };
|
|
329
|
+
message: describeEnvironmentCollisions(collisions, secretNames) };
|
|
312
330
|
}
|
|
313
331
|
for (const key of keys) claimedKeys.add(key);
|
|
314
332
|
if (resolution.status === 'composed') {
|
package/ts/requests/admin.ts
CHANGED
|
@@ -8,6 +8,9 @@ import type {
|
|
|
8
8
|
IDeploymentMachineUserMetadata, IDeploymentMachineUserMutationResult,
|
|
9
9
|
TDeploymentMachineUserMutation,
|
|
10
10
|
} from '../data/deploymentmachineuser.js';
|
|
11
|
+
import type {
|
|
12
|
+
IDelegatedAdminTokenMetadata, IDelegatedAdminTokenMint, IDelegatedAdminTokenMintResult,
|
|
13
|
+
} from '../data/delegatedadmintoken.js';
|
|
11
14
|
|
|
12
15
|
export interface IReq_Admin_GetServiceMachineCredential extends plugins.typedrequestInterfaces.implementsTR<
|
|
13
16
|
plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_GetServiceMachineCredential
|
|
@@ -198,3 +201,51 @@ export interface IReq_Cloudly_Coreflow_ReconcileBaseServices extends plugins.typ
|
|
|
198
201
|
routingProof?: ICoreflowRoutingProofResult;
|
|
199
202
|
};
|
|
200
203
|
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Mints one delegated admin token (`delegatedAdminTokenContract`) for what the request states
|
|
207
|
+
* (`IDelegatedAdminTokenMint`: its scopes, optional `lifetimeMs` and `secretKeyPrefixes`). Requires
|
|
208
|
+
* a human administrator who signed in within five minutes; a delegated session is refused. The
|
|
209
|
+
* plaintext token is answered on this answer only and is never retrievable again, so the request is
|
|
210
|
+
* sent once and its answer goes past every traffic hook. `lifetimeMs` absent is
|
|
211
|
+
* `defaultLifetimeMs`; Cloudly refuses any lifetime above `maximumLifetimeMs`. Refusals travel as
|
|
212
|
+
* `IDelegatedAdminTokenErrorData`.
|
|
213
|
+
*/
|
|
214
|
+
export interface IReq_Admin_MintDelegatedAdminToken extends plugins.typedrequestInterfaces.implementsTR<
|
|
215
|
+
plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_MintDelegatedAdminToken
|
|
216
|
+
> {
|
|
217
|
+
method: 'mintDelegatedAdminToken';
|
|
218
|
+
request: { identity: IIdentityCredential } & IDelegatedAdminTokenMint;
|
|
219
|
+
response: IDelegatedAdminTokenMintResult;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Lists the active delegated admin tokens, value-free, newest first: not expired, not revoked,
|
|
224
|
+
* and their administrator still a human administrator at the credential generation each was
|
|
225
|
+
* minted under. At most `delegatedAdminTokenContract.maximumListedTokens` are answered;
|
|
226
|
+
* `truncated` is true exactly when active tokens beyond them were left out, so no token that
|
|
227
|
+
* needs revoking is hidden without notice. Requires an administrator; a delegated session is
|
|
228
|
+
* refused. Refusals travel as `IDelegatedAdminTokenErrorData`.
|
|
229
|
+
*/
|
|
230
|
+
export interface IReq_Admin_GetDelegatedAdminTokens extends plugins.typedrequestInterfaces.implementsTR<
|
|
231
|
+
plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_GetDelegatedAdminTokens
|
|
232
|
+
> {
|
|
233
|
+
method: 'getDelegatedAdminTokens';
|
|
234
|
+
request: { identity: IIdentityCredential };
|
|
235
|
+
response: { tokens: IDelegatedAdminTokenMetadata[]; truncated: boolean };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Revokes one active delegated admin token; once the revocation commits, no use of the token
|
|
240
|
+
* commits after it, and every session exchanged for it is refused. A success has committed.
|
|
241
|
+
* Requires an administrator, the minting one or any other; a delegated session is refused.
|
|
242
|
+
* Refusals travel as `IDelegatedAdminTokenErrorData`: a token that already does not act (revoked,
|
|
243
|
+
* expired, or its administrator's credential or role changed) is `DELEGATED_ADMIN_TOKEN_INACTIVE`.
|
|
244
|
+
*/
|
|
245
|
+
export interface IReq_Admin_RevokeDelegatedAdminToken extends plugins.typedrequestInterfaces.implementsTR<
|
|
246
|
+
plugins.typedrequestInterfaces.ITypedRequest, IReq_Admin_RevokeDelegatedAdminToken
|
|
247
|
+
> {
|
|
248
|
+
method: 'revokeDelegatedAdminToken';
|
|
249
|
+
request: { identity: IIdentityCredential; tokenId: string };
|
|
250
|
+
response: { metadata: IDelegatedAdminTokenMetadata };
|
|
251
|
+
}
|
|
@@ -12,7 +12,7 @@ export interface IReq_GetRegistryById extends plugins.typedrequestInterfaces.imp
|
|
|
12
12
|
id: string;
|
|
13
13
|
};
|
|
14
14
|
response: {
|
|
15
|
-
registry: data.
|
|
15
|
+
registry: data.IExternalRegistryPublic;
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -25,7 +25,7 @@ export interface IReq_GetRegistries extends plugins.typedrequestInterfaces.imple
|
|
|
25
25
|
identity: userInterfaces.IIdentity;
|
|
26
26
|
};
|
|
27
27
|
response: {
|
|
28
|
-
registries: data.
|
|
28
|
+
registries: data.IExternalRegistryPublic[];
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -39,7 +39,7 @@ export interface IReq_CreateRegistry extends plugins.typedrequestInterfaces.impl
|
|
|
39
39
|
registryData: data.IExternalRegistry['data'];
|
|
40
40
|
};
|
|
41
41
|
response: {
|
|
42
|
-
registry: data.
|
|
42
|
+
registry: data.IExternalRegistryPublic;
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -51,10 +51,14 @@ export interface IReq_UpdateRegistry extends plugins.typedrequestInterfaces.impl
|
|
|
51
51
|
request: {
|
|
52
52
|
identity: userInterfaces.IIdentity;
|
|
53
53
|
registryId: string;
|
|
54
|
-
|
|
54
|
+
/**
|
|
55
|
+
* The operator fields to change; a field left out keeps its stored value. `password` is
|
|
56
|
+
* write-only: missing or empty keeps the stored credential, and `authType: 'none'` removes it.
|
|
57
|
+
*/
|
|
58
|
+
registryData: Partial<data.TExternalRegistryOperatorData>;
|
|
55
59
|
};
|
|
56
60
|
response: {
|
|
57
|
-
resultRegistry: data.
|
|
61
|
+
resultRegistry: data.IExternalRegistryPublic;
|
|
58
62
|
};
|
|
59
63
|
}
|
|
60
64
|
|
|
@@ -82,8 +86,10 @@ export interface IReq_VerifyRegistry extends plugins.typedrequestInterfaces.impl
|
|
|
82
86
|
registryId: string;
|
|
83
87
|
};
|
|
84
88
|
response: {
|
|
89
|
+
/** True exactly for the `authenticated` and `anonymous` outcomes. */
|
|
85
90
|
success: boolean;
|
|
91
|
+
outcome: data.TExternalRegistryVerifyOutcome;
|
|
86
92
|
message?: string;
|
|
87
|
-
registry
|
|
93
|
+
registry: data.IExternalRegistryPublic;
|
|
88
94
|
};
|
|
89
95
|
}
|
package/ts/requests/identity.ts
CHANGED
|
@@ -7,6 +7,10 @@ import * as userInterfaces from '../data/user.js'
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* get the identity that then will be used to get the config
|
|
10
|
+
*
|
|
11
|
+
* A delegated admin token (`delegatedAdminTokenContract`) is exchanged here too. Its identity is
|
|
12
|
+
* `type: 'machine'`, `role: 'api'`, `userId: 'delegated-admin-token:<tokenId>'`, expiring with the
|
|
13
|
+
* token; only the requests of the token's scopes admit it, and each re-reads the token.
|
|
10
14
|
*/
|
|
11
15
|
export interface IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken
|
|
12
16
|
extends plugins.typedrequestInterfaces.implementsTR<
|
package/ts/requests/service.ts
CHANGED
|
@@ -455,7 +455,22 @@ extends plugins.typedrequestInterfaces.implementsTR<
|
|
|
455
455
|
request: {
|
|
456
456
|
identity: IIdentity;
|
|
457
457
|
serviceId: string;
|
|
458
|
-
|
|
458
|
+
/**
|
|
459
|
+
* The fields to change, merged over the stored service one key deep: a key the update omits
|
|
460
|
+
* keeps its stored value, and a key it states replaces the stored value whole (a stated
|
|
461
|
+
* `ports`, `mail` or `domains` is the complete new value, not a patch of the old one).
|
|
462
|
+
* `containerArgs: null` removes the stored argument vector; no other field takes `null`.
|
|
463
|
+
* `registryTarget` is the exception to "replaces": Cloudly derives it from the service and
|
|
464
|
+
* its image version, so a stated one only makes Cloudly derive it again and is never stored.
|
|
465
|
+
* An optional field cannot be cleared yet: a key whose value is `undefined` is dropped in
|
|
466
|
+
* transit, so it reads as omitted (explicit `null` removal for optional fields is a follow-up).
|
|
467
|
+
*
|
|
468
|
+
* Cloudly's guards ask which keys an update states, so send only the fields that change: a
|
|
469
|
+
* service with a runtime spec refuses any update stating `placement` or `scaleFactor`, and an
|
|
470
|
+
* immutable service refuses `imageId`, `imageVersion`, `registryTarget`, `scaleFactor` and
|
|
471
|
+
* `placement`, even when the stated value repeats the stored one.
|
|
472
|
+
*/
|
|
473
|
+
serviceData: Partial<TServiceUpdateWritableData>;
|
|
459
474
|
};
|
|
460
475
|
response: {
|
|
461
476
|
service: IService;
|