@serve.zone/interfaces 23.0.4 → 23.2.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 +17 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/immutableimage.d.ts +2 -0
- package/dist_ts/data/immutableimage.js +1 -1
- package/dist_ts/data/secret.d.ts +2 -0
- package/dist_ts/data/secret.js +18 -3
- package/dist_ts/runtime.d.ts +123 -1
- package/dist_ts/runtime.js +525 -2
- package/package.json +1 -1
- package/readme.md +47 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/immutableimage.ts +2 -0
- package/ts/data/secret.ts +24 -2
- package/ts/runtime.ts +772 -0
package/ts/runtime.ts
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import type {
|
|
3
|
+
IActiveSecretRecipientMetadata,
|
|
4
|
+
IClusterSecretDeploymentState,
|
|
5
|
+
ICoreflowRuntimeCapabilities,
|
|
3
6
|
IIdentityCredential,
|
|
4
7
|
IResolvedSecretManifest,
|
|
8
|
+
IResolvedSecretManifestReference,
|
|
5
9
|
TSha256Digest,
|
|
10
|
+
TImmutableContainerPlatform,
|
|
6
11
|
TSecretRecipientMetadata,
|
|
7
12
|
} from './data/index.js';
|
|
8
13
|
import {
|
|
9
14
|
createResolvedSecretMaterialEnvelopeContext,
|
|
10
15
|
createSecretRecipientEnrollmentChallengeContext,
|
|
16
|
+
resolvedSecretManifestReferencesEqual,
|
|
11
17
|
validateResolvedSecretManifest,
|
|
18
|
+
validateResolvedSecretManifestReference,
|
|
12
19
|
validateSecretRecipientMetadata,
|
|
20
|
+
validateSecretRecipientSet,
|
|
13
21
|
verifySecretEnvelopeContext,
|
|
14
22
|
verifyResolvedSecretManifestDigest,
|
|
15
23
|
} from './data/secret.js';
|
|
24
|
+
import { isSha256Digest } from './data/immutableimage.js';
|
|
16
25
|
|
|
17
26
|
export interface ISealedResolvedSecretMaterialEntry {
|
|
18
27
|
secretVersionId: string;
|
|
@@ -351,3 +360,766 @@ export const validateCompleteSecretRecipientEnrollmentRequest = (
|
|
|
351
360
|
};
|
|
352
361
|
|
|
353
362
|
export { validateSecretRecipientMetadata };
|
|
363
|
+
|
|
364
|
+
const runtimeIdentifierRegex = /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/;
|
|
365
|
+
const runtimeFailureCodeRegex = /^[A-Z][A-Z0-9_]{0,127}$/;
|
|
366
|
+
const runtimePlatforms = new Set<TImmutableContainerPlatform>([
|
|
367
|
+
'linux/amd64',
|
|
368
|
+
'linux/arm64',
|
|
369
|
+
]);
|
|
370
|
+
const maximumRuntimeNodes = 1024;
|
|
371
|
+
|
|
372
|
+
const isRuntimeRecord = (valueArg: unknown): valueArg is Record<string, unknown> => (
|
|
373
|
+
Boolean(valueArg) && typeof valueArg === 'object' && !Array.isArray(valueArg)
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
const hasExactRuntimeKeys = (
|
|
377
|
+
valueArg: Record<string, unknown>,
|
|
378
|
+
keysArg: string[],
|
|
379
|
+
): boolean => JSON.stringify(Object.keys(valueArg).sort()) === JSON.stringify([...keysArg].sort());
|
|
380
|
+
|
|
381
|
+
const hasOnlyRuntimeKeys = (
|
|
382
|
+
valueArg: Record<string, unknown>,
|
|
383
|
+
keysArg: string[],
|
|
384
|
+
): boolean => Object.keys(valueArg).every((keyArg) => keysArg.includes(keyArg));
|
|
385
|
+
|
|
386
|
+
const isRuntimeIdentifier = (valueArg: unknown): valueArg is string => (
|
|
387
|
+
typeof valueArg === 'string' && runtimeIdentifierRegex.test(valueArg)
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const isPositiveSafeInteger = (valueArg: unknown): valueArg is number => (
|
|
391
|
+
Number.isSafeInteger(valueArg) && (valueArg as number) > 0
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
const computeRuntimeSha256 = async (inputArg: string): Promise<TSha256Digest> => {
|
|
395
|
+
const input = new TextEncoder().encode(inputArg);
|
|
396
|
+
const digest = new Uint8Array(await globalThis.crypto.subtle.digest(
|
|
397
|
+
'SHA-256',
|
|
398
|
+
input.buffer as ArrayBuffer,
|
|
399
|
+
));
|
|
400
|
+
return `sha256:${[...digest]
|
|
401
|
+
.map((byteArg) => byteArg.toString(16).padStart(2, '0'))
|
|
402
|
+
.join('')}` as TSha256Digest;
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
export type TSecretRecipientEnrollmentStateV1 =
|
|
406
|
+
| {
|
|
407
|
+
schemaVersion: 1;
|
|
408
|
+
generation: 0;
|
|
409
|
+
recipients: [];
|
|
410
|
+
}
|
|
411
|
+
| {
|
|
412
|
+
schemaVersion: 1;
|
|
413
|
+
generation: number;
|
|
414
|
+
recipients: TSecretRecipientMetadata[];
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
/** Cluster scope is derived exclusively from the verified cluster JWT. */
|
|
418
|
+
export interface IReq_GetSecretRecipientEnrollmentState
|
|
419
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
420
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
421
|
+
IReq_GetSecretRecipientEnrollmentState
|
|
422
|
+
> {
|
|
423
|
+
method: 'getSecretRecipientEnrollmentState';
|
|
424
|
+
request: {
|
|
425
|
+
identity: IIdentityCredential;
|
|
426
|
+
};
|
|
427
|
+
response: TSecretRecipientEnrollmentStateV1;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export const validateGetSecretRecipientEnrollmentStateRequest = (
|
|
431
|
+
requestArg: unknown,
|
|
432
|
+
): string[] => {
|
|
433
|
+
try {
|
|
434
|
+
if (!isRuntimeRecord(requestArg)
|
|
435
|
+
|| !hasExactRuntimeKeys(requestArg, ['identity'])
|
|
436
|
+
|| !isRuntimeRecord(requestArg.identity)
|
|
437
|
+
|| !hasExactRuntimeKeys(requestArg.identity, ['jwt'])
|
|
438
|
+
|| typeof requestArg.identity.jwt !== 'string'
|
|
439
|
+
|| requestArg.identity.jwt.length === 0) {
|
|
440
|
+
return ['secret recipient enrollment state request must contain only a JWT identity'];
|
|
441
|
+
}
|
|
442
|
+
return [];
|
|
443
|
+
} catch {
|
|
444
|
+
return ['secret recipient enrollment state request must be safely inspectable'];
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
export const validateSecretRecipientEnrollmentState = (
|
|
449
|
+
stateArg: unknown,
|
|
450
|
+
): string[] => {
|
|
451
|
+
try {
|
|
452
|
+
if (!isRuntimeRecord(stateArg)
|
|
453
|
+
|| !hasExactRuntimeKeys(stateArg, ['schemaVersion', 'generation', 'recipients'])) {
|
|
454
|
+
return ['secret recipient enrollment state must use its exact schema'];
|
|
455
|
+
}
|
|
456
|
+
const errors: string[] = [];
|
|
457
|
+
if (stateArg.schemaVersion !== 1) {
|
|
458
|
+
errors.push('secret recipient enrollment state schemaVersion must be 1');
|
|
459
|
+
}
|
|
460
|
+
if (stateArg.generation === 0) {
|
|
461
|
+
if (!Array.isArray(stateArg.recipients) || stateArg.recipients.length !== 0) {
|
|
462
|
+
errors.push('generation-zero enrollment state must have no recipients');
|
|
463
|
+
}
|
|
464
|
+
return errors;
|
|
465
|
+
}
|
|
466
|
+
if (!isPositiveSafeInteger(stateArg.generation)) {
|
|
467
|
+
errors.push('secret recipient enrollment state generation must be zero or positive');
|
|
468
|
+
}
|
|
469
|
+
const recipientErrors = validateSecretRecipientSet(stateArg.recipients);
|
|
470
|
+
errors.push(...recipientErrors.map((errorArg) => `enrollment state ${errorArg}`));
|
|
471
|
+
if (recipientErrors.length === 0) {
|
|
472
|
+
const recipients = stateArg.recipients as TSecretRecipientMetadata[];
|
|
473
|
+
const greatestGeneration = Math.max(...recipients.map((recipientArg) => recipientArg.generation));
|
|
474
|
+
if (stateArg.generation !== greatestGeneration) {
|
|
475
|
+
errors.push('secret recipient enrollment state generation must match the active generation');
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return errors;
|
|
479
|
+
} catch {
|
|
480
|
+
return ['secret recipient enrollment state must be safely inspectable'];
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
export interface ICoreflowSecretRuntimeNodeTargetV1 {
|
|
485
|
+
nodeId: string;
|
|
486
|
+
nodeName: string;
|
|
487
|
+
platform: TImmutableContainerPlatform;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export interface ICoreflowSecretRuntimeNodeEvidenceV1
|
|
491
|
+
extends ICoreflowSecretRuntimeNodeTargetV1 {
|
|
492
|
+
workloadInitPlatformManifestDigest: TSha256Digest;
|
|
493
|
+
workloadInitExecutableDigest: TSha256Digest;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export interface ICoreflowSecretRuntimeArtifactV1 {
|
|
497
|
+
platform: TImmutableContainerPlatform;
|
|
498
|
+
workloadInitPlatformManifestDigest: TSha256Digest;
|
|
499
|
+
workloadInitExecutableDigest: TSha256Digest;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export interface ICoreflowSecretRuntimeRegistrationExpectationV1 {
|
|
503
|
+
expectationVersion: 1;
|
|
504
|
+
reporterSessionId: string;
|
|
505
|
+
targets: ICoreflowSecretRuntimeNodeTargetV1[];
|
|
506
|
+
workloadInitImageIndexDigest: TSha256Digest;
|
|
507
|
+
workloadInitArtifacts: ICoreflowSecretRuntimeArtifactV1[];
|
|
508
|
+
activeRecipient: IActiveSecretRecipientMetadata;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** Dedicated TypedSocket tag carrying ICoreflowSecretRuntimeRegistrationV1. */
|
|
512
|
+
export const coreflowSecretRuntimeRegistrationTagId =
|
|
513
|
+
'coreflowSecretRuntimeRegistration' as const;
|
|
514
|
+
|
|
515
|
+
/** Cluster scope is derived exclusively from the verified cluster JWT. */
|
|
516
|
+
export interface IReq_GetCoreflowSecretRuntimeRegistrationExpectation
|
|
517
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
518
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
519
|
+
IReq_GetCoreflowSecretRuntimeRegistrationExpectation
|
|
520
|
+
> {
|
|
521
|
+
method: 'getCoreflowSecretRuntimeRegistrationExpectation';
|
|
522
|
+
request: {
|
|
523
|
+
identity: IIdentityCredential;
|
|
524
|
+
};
|
|
525
|
+
response: {
|
|
526
|
+
expectation: ICoreflowSecretRuntimeRegistrationExpectationV1;
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export const validateGetCoreflowSecretRuntimeRegistrationExpectationRequest = (
|
|
531
|
+
requestArg: unknown,
|
|
532
|
+
): string[] => {
|
|
533
|
+
try {
|
|
534
|
+
if (!isRuntimeRecord(requestArg)
|
|
535
|
+
|| !hasExactRuntimeKeys(requestArg, ['identity'])
|
|
536
|
+
|| !isRuntimeRecord(requestArg.identity)
|
|
537
|
+
|| !hasExactRuntimeKeys(requestArg.identity, ['jwt'])
|
|
538
|
+
|| typeof requestArg.identity.jwt !== 'string'
|
|
539
|
+
|| requestArg.identity.jwt.length === 0) {
|
|
540
|
+
return ['secret runtime registration expectation request must contain only a JWT identity'];
|
|
541
|
+
}
|
|
542
|
+
return [];
|
|
543
|
+
} catch {
|
|
544
|
+
return ['secret runtime registration expectation request must be safely inspectable'];
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
export interface ICoreflowSecretRuntimeRegistrationV1 {
|
|
549
|
+
registrationVersion: 1;
|
|
550
|
+
reporterSessionId: string;
|
|
551
|
+
registeredAt: number;
|
|
552
|
+
targets: ICoreflowSecretRuntimeNodeTargetV1[];
|
|
553
|
+
nodeSetDigest: TSha256Digest;
|
|
554
|
+
nodeEvidence: ICoreflowSecretRuntimeNodeEvidenceV1[];
|
|
555
|
+
capabilities: ICoreflowRuntimeCapabilities;
|
|
556
|
+
activeRecipient: IActiveSecretRecipientMetadata;
|
|
557
|
+
workloadInitImageIndexDigest: TSha256Digest;
|
|
558
|
+
workloadInitApprovedAt: number;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const compareRuntimeTargets = (
|
|
562
|
+
leftArg: ICoreflowSecretRuntimeNodeTargetV1,
|
|
563
|
+
rightArg: ICoreflowSecretRuntimeNodeTargetV1,
|
|
564
|
+
): number => {
|
|
565
|
+
for (const [left, right] of [
|
|
566
|
+
[leftArg.nodeId, rightArg.nodeId],
|
|
567
|
+
[leftArg.nodeName, rightArg.nodeName],
|
|
568
|
+
[leftArg.platform, rightArg.platform],
|
|
569
|
+
]) {
|
|
570
|
+
if (left < right) return -1;
|
|
571
|
+
if (left > right) return 1;
|
|
572
|
+
}
|
|
573
|
+
return 0;
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
const canonicalRuntimeTarget = (
|
|
577
|
+
targetArg: ICoreflowSecretRuntimeNodeTargetV1,
|
|
578
|
+
): ICoreflowSecretRuntimeNodeTargetV1 => ({
|
|
579
|
+
nodeId: targetArg.nodeId,
|
|
580
|
+
nodeName: targetArg.nodeName,
|
|
581
|
+
platform: targetArg.platform,
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
export const createCoreflowSecretRuntimeNodeSetDigestInput = (
|
|
585
|
+
targetsArg: ICoreflowSecretRuntimeNodeTargetV1[],
|
|
586
|
+
): string => JSON.stringify({
|
|
587
|
+
schemaVersion: 1,
|
|
588
|
+
targets: targetsArg.map(canonicalRuntimeTarget),
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
export const computeCoreflowSecretRuntimeNodeSetDigest = async (
|
|
592
|
+
targetsArg: ICoreflowSecretRuntimeNodeTargetV1[],
|
|
593
|
+
): Promise<TSha256Digest> => computeRuntimeSha256(
|
|
594
|
+
createCoreflowSecretRuntimeNodeSetDigestInput(targetsArg),
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
const validateRuntimeTargets = (
|
|
598
|
+
targetsArg: unknown,
|
|
599
|
+
pathArg: string,
|
|
600
|
+
): string[] => {
|
|
601
|
+
if (!Array.isArray(targetsArg)
|
|
602
|
+
|| targetsArg.length === 0
|
|
603
|
+
|| targetsArg.length > maximumRuntimeNodes) {
|
|
604
|
+
return [`${pathArg} must be a non-empty bounded array`];
|
|
605
|
+
}
|
|
606
|
+
const errors: string[] = [];
|
|
607
|
+
let previous: ICoreflowSecretRuntimeNodeTargetV1 | undefined;
|
|
608
|
+
const nodeIds = new Set<string>();
|
|
609
|
+
const nodeNames = new Set<string>();
|
|
610
|
+
for (const [index, targetArg] of targetsArg.entries()) {
|
|
611
|
+
if (!isRuntimeRecord(targetArg)
|
|
612
|
+
|| !hasExactRuntimeKeys(targetArg, ['nodeId', 'nodeName', 'platform'])) {
|
|
613
|
+
errors.push(`${pathArg}[${index}] must use its exact schema`);
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
if (!isRuntimeIdentifier(targetArg.nodeId) || !isRuntimeIdentifier(targetArg.nodeName)) {
|
|
617
|
+
errors.push(`${pathArg}[${index}] node identity must be canonical`);
|
|
618
|
+
}
|
|
619
|
+
if (!runtimePlatforms.has(targetArg.platform as TImmutableContainerPlatform)) {
|
|
620
|
+
errors.push(`${pathArg}[${index}].platform must be supported`);
|
|
621
|
+
}
|
|
622
|
+
if (isRuntimeIdentifier(targetArg.nodeId)
|
|
623
|
+
&& isRuntimeIdentifier(targetArg.nodeName)
|
|
624
|
+
&& runtimePlatforms.has(targetArg.platform as TImmutableContainerPlatform)) {
|
|
625
|
+
const target = targetArg as unknown as ICoreflowSecretRuntimeNodeTargetV1;
|
|
626
|
+
if (previous && compareRuntimeTargets(previous, target) >= 0) {
|
|
627
|
+
errors.push(`${pathArg} must be uniquely sorted by nodeId, nodeName, and platform`);
|
|
628
|
+
}
|
|
629
|
+
previous = target;
|
|
630
|
+
if (nodeIds.has(target.nodeId)) errors.push(`${pathArg} nodeIds must be unique`);
|
|
631
|
+
if (nodeNames.has(target.nodeName)) errors.push(`${pathArg} nodeNames must be unique`);
|
|
632
|
+
nodeIds.add(target.nodeId);
|
|
633
|
+
nodeNames.add(target.nodeName);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return errors;
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
const validateRuntimeArtifacts = (
|
|
640
|
+
artifactsArg: unknown,
|
|
641
|
+
pathArg: string,
|
|
642
|
+
): string[] => {
|
|
643
|
+
if (!Array.isArray(artifactsArg)
|
|
644
|
+
|| artifactsArg.length === 0
|
|
645
|
+
|| artifactsArg.length > runtimePlatforms.size) {
|
|
646
|
+
return [`${pathArg} must be a non-empty bounded array`];
|
|
647
|
+
}
|
|
648
|
+
const errors: string[] = [];
|
|
649
|
+
const platforms: string[] = [];
|
|
650
|
+
for (const [index, artifactArg] of artifactsArg.entries()) {
|
|
651
|
+
if (!isRuntimeRecord(artifactArg)
|
|
652
|
+
|| !hasExactRuntimeKeys(artifactArg, [
|
|
653
|
+
'platform',
|
|
654
|
+
'workloadInitPlatformManifestDigest',
|
|
655
|
+
'workloadInitExecutableDigest',
|
|
656
|
+
])) {
|
|
657
|
+
errors.push(`${pathArg}[${index}] must use its exact schema`);
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
platforms.push(artifactArg.platform as string);
|
|
661
|
+
if (!runtimePlatforms.has(artifactArg.platform as TImmutableContainerPlatform)) {
|
|
662
|
+
errors.push(`${pathArg}[${index}].platform must be supported`);
|
|
663
|
+
}
|
|
664
|
+
if (typeof artifactArg.workloadInitPlatformManifestDigest !== 'string'
|
|
665
|
+
|| !isSha256Digest(artifactArg.workloadInitPlatformManifestDigest)
|
|
666
|
+
|| typeof artifactArg.workloadInitExecutableDigest !== 'string'
|
|
667
|
+
|| !isSha256Digest(artifactArg.workloadInitExecutableDigest)) {
|
|
668
|
+
errors.push(`${pathArg}[${index}] digests must be canonical`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (new Set(platforms).size !== platforms.length
|
|
672
|
+
|| JSON.stringify(platforms) !== JSON.stringify([...platforms].sort())) {
|
|
673
|
+
errors.push(`${pathArg} must be uniquely sorted by platform`);
|
|
674
|
+
}
|
|
675
|
+
return errors;
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
const activeRecipientFingerprint = (recipientArg: IActiveSecretRecipientMetadata): string => (
|
|
679
|
+
JSON.stringify({
|
|
680
|
+
schemaVersion: recipientArg.schemaVersion,
|
|
681
|
+
recipientKeyId: recipientArg.recipientKeyId,
|
|
682
|
+
publicKey: recipientArg.publicKey,
|
|
683
|
+
generation: recipientArg.generation,
|
|
684
|
+
activatedAt: recipientArg.activatedAt,
|
|
685
|
+
lifecycleState: recipientArg.lifecycleState,
|
|
686
|
+
})
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Consumers discard this registration on transport disconnect, tag
|
|
691
|
+
* removal/replacement, reporter-session change, or any expectation change.
|
|
692
|
+
*/
|
|
693
|
+
export const validateCoreflowSecretRuntimeRegistration = async (
|
|
694
|
+
registrationArg: unknown,
|
|
695
|
+
expectationArg: unknown,
|
|
696
|
+
): Promise<string[]> => {
|
|
697
|
+
try {
|
|
698
|
+
const errors: string[] = [];
|
|
699
|
+
if (!isRuntimeRecord(expectationArg)
|
|
700
|
+
|| !hasExactRuntimeKeys(expectationArg, [
|
|
701
|
+
'expectationVersion',
|
|
702
|
+
'reporterSessionId',
|
|
703
|
+
'targets',
|
|
704
|
+
'workloadInitImageIndexDigest',
|
|
705
|
+
'workloadInitArtifacts',
|
|
706
|
+
'activeRecipient',
|
|
707
|
+
])) {
|
|
708
|
+
return ['secret runtime registration expectation must use its exact schema'];
|
|
709
|
+
}
|
|
710
|
+
if (expectationArg.expectationVersion !== 1) {
|
|
711
|
+
errors.push('secret runtime registration expectationVersion must be 1');
|
|
712
|
+
}
|
|
713
|
+
if (!isRuntimeIdentifier(expectationArg.reporterSessionId)) {
|
|
714
|
+
errors.push('expected secret runtime reporterSessionId must be canonical');
|
|
715
|
+
}
|
|
716
|
+
const expectedTargetErrors = validateRuntimeTargets(expectationArg.targets, 'expected targets');
|
|
717
|
+
errors.push(...expectedTargetErrors);
|
|
718
|
+
errors.push(...validateRuntimeArtifacts(
|
|
719
|
+
expectationArg.workloadInitArtifacts,
|
|
720
|
+
'expected WorkloadInit artifacts',
|
|
721
|
+
));
|
|
722
|
+
if (typeof expectationArg.workloadInitImageIndexDigest !== 'string'
|
|
723
|
+
|| !isSha256Digest(expectationArg.workloadInitImageIndexDigest)) {
|
|
724
|
+
errors.push('expected WorkloadInit image index digest must be canonical');
|
|
725
|
+
}
|
|
726
|
+
const expectedRecipientErrors = validateSecretRecipientMetadata(expectationArg.activeRecipient);
|
|
727
|
+
if (expectedRecipientErrors.length > 0
|
|
728
|
+
|| !isRuntimeRecord(expectationArg.activeRecipient)
|
|
729
|
+
|| expectationArg.activeRecipient.lifecycleState !== 'active') {
|
|
730
|
+
errors.push('expected secret recipient must be canonical and active');
|
|
731
|
+
}
|
|
732
|
+
if (!isRuntimeRecord(registrationArg)
|
|
733
|
+
|| !hasExactRuntimeKeys(registrationArg, [
|
|
734
|
+
'registrationVersion',
|
|
735
|
+
'reporterSessionId',
|
|
736
|
+
'registeredAt',
|
|
737
|
+
'targets',
|
|
738
|
+
'nodeSetDigest',
|
|
739
|
+
'nodeEvidence',
|
|
740
|
+
'capabilities',
|
|
741
|
+
'activeRecipient',
|
|
742
|
+
'workloadInitImageIndexDigest',
|
|
743
|
+
'workloadInitApprovedAt',
|
|
744
|
+
])) {
|
|
745
|
+
errors.push('secret runtime registration must use its exact schema');
|
|
746
|
+
return errors;
|
|
747
|
+
}
|
|
748
|
+
if (registrationArg.registrationVersion !== 1) {
|
|
749
|
+
errors.push('secret runtime registrationVersion must be 1');
|
|
750
|
+
}
|
|
751
|
+
if (!isRuntimeIdentifier(registrationArg.reporterSessionId)) {
|
|
752
|
+
errors.push('secret runtime reporterSessionId must be canonical');
|
|
753
|
+
} else if (registrationArg.reporterSessionId !== expectationArg.reporterSessionId) {
|
|
754
|
+
errors.push('secret runtime reporterSessionId does not match the live session');
|
|
755
|
+
}
|
|
756
|
+
if (!isPositiveSafeInteger(registrationArg.registeredAt)
|
|
757
|
+
|| !isPositiveSafeInteger(registrationArg.workloadInitApprovedAt)) {
|
|
758
|
+
errors.push('secret runtime registration timestamps must be positive integers');
|
|
759
|
+
}
|
|
760
|
+
const registrationTargetErrors = validateRuntimeTargets(
|
|
761
|
+
registrationArg.targets,
|
|
762
|
+
'registration targets',
|
|
763
|
+
);
|
|
764
|
+
errors.push(...registrationTargetErrors);
|
|
765
|
+
if (typeof registrationArg.nodeSetDigest !== 'string'
|
|
766
|
+
|| !isSha256Digest(registrationArg.nodeSetDigest)) {
|
|
767
|
+
errors.push('secret runtime nodeSetDigest must be canonical');
|
|
768
|
+
}
|
|
769
|
+
if (typeof registrationArg.workloadInitImageIndexDigest !== 'string'
|
|
770
|
+
|| !isSha256Digest(registrationArg.workloadInitImageIndexDigest)) {
|
|
771
|
+
errors.push('secret runtime WorkloadInit image index digest must be canonical');
|
|
772
|
+
}
|
|
773
|
+
const registrationRecipientErrors = validateSecretRecipientMetadata(registrationArg.activeRecipient);
|
|
774
|
+
if (registrationRecipientErrors.length > 0
|
|
775
|
+
|| !isRuntimeRecord(registrationArg.activeRecipient)
|
|
776
|
+
|| registrationArg.activeRecipient.lifecycleState !== 'active') {
|
|
777
|
+
errors.push('secret runtime registration recipient must be canonical and active');
|
|
778
|
+
}
|
|
779
|
+
if (!isRuntimeRecord(registrationArg.capabilities)
|
|
780
|
+
|| !hasOnlyRuntimeKeys(registrationArg.capabilities, [
|
|
781
|
+
'immutableImageDeploymentVersion',
|
|
782
|
+
'corestoreInventoryVersion',
|
|
783
|
+
'secretManifestVersion',
|
|
784
|
+
'sealedSecretMaterialVersion',
|
|
785
|
+
'secretRecipientEnrollmentVersion',
|
|
786
|
+
'workloadInitEnvironmentVersion',
|
|
787
|
+
'secretDeploymentReportVersion',
|
|
788
|
+
])
|
|
789
|
+
|| registrationArg.capabilities.immutableImageDeploymentVersion !== 1
|
|
790
|
+
|| registrationArg.capabilities.secretManifestVersion !== 2
|
|
791
|
+
|| registrationArg.capabilities.sealedSecretMaterialVersion !== 1
|
|
792
|
+
|| registrationArg.capabilities.secretRecipientEnrollmentVersion !== 1
|
|
793
|
+
|| registrationArg.capabilities.workloadInitEnvironmentVersion !== 1
|
|
794
|
+
|| registrationArg.capabilities.secretDeploymentReportVersion !== 1
|
|
795
|
+
|| (registrationArg.capabilities.corestoreInventoryVersion !== undefined
|
|
796
|
+
&& registrationArg.capabilities.corestoreInventoryVersion !== 1)) {
|
|
797
|
+
errors.push('secret runtime registration capabilities are incomplete or unsupported');
|
|
798
|
+
}
|
|
799
|
+
if (!Array.isArray(registrationArg.nodeEvidence)
|
|
800
|
+
|| registrationArg.nodeEvidence.length === 0
|
|
801
|
+
|| registrationArg.nodeEvidence.length > maximumRuntimeNodes) {
|
|
802
|
+
errors.push('secret runtime node evidence must be a non-empty bounded array');
|
|
803
|
+
} else {
|
|
804
|
+
const evidenceTargets: ICoreflowSecretRuntimeNodeTargetV1[] = [];
|
|
805
|
+
const evidenceArtifacts: ICoreflowSecretRuntimeArtifactV1[] = [];
|
|
806
|
+
for (const [index, evidenceArg] of registrationArg.nodeEvidence.entries()) {
|
|
807
|
+
if (!isRuntimeRecord(evidenceArg)
|
|
808
|
+
|| !hasExactRuntimeKeys(evidenceArg, [
|
|
809
|
+
'nodeId',
|
|
810
|
+
'nodeName',
|
|
811
|
+
'platform',
|
|
812
|
+
'workloadInitPlatformManifestDigest',
|
|
813
|
+
'workloadInitExecutableDigest',
|
|
814
|
+
])) {
|
|
815
|
+
errors.push(`secret runtime nodeEvidence[${index}] must use its exact schema`);
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
evidenceTargets.push({
|
|
819
|
+
nodeId: evidenceArg.nodeId as string,
|
|
820
|
+
nodeName: evidenceArg.nodeName as string,
|
|
821
|
+
platform: evidenceArg.platform as TImmutableContainerPlatform,
|
|
822
|
+
});
|
|
823
|
+
evidenceArtifacts.push({
|
|
824
|
+
platform: evidenceArg.platform as TImmutableContainerPlatform,
|
|
825
|
+
workloadInitPlatformManifestDigest:
|
|
826
|
+
evidenceArg.workloadInitPlatformManifestDigest as TSha256Digest,
|
|
827
|
+
workloadInitExecutableDigest: evidenceArg.workloadInitExecutableDigest as TSha256Digest,
|
|
828
|
+
});
|
|
829
|
+
if (typeof evidenceArg.workloadInitPlatformManifestDigest !== 'string'
|
|
830
|
+
|| !isSha256Digest(evidenceArg.workloadInitPlatformManifestDigest)
|
|
831
|
+
|| typeof evidenceArg.workloadInitExecutableDigest !== 'string'
|
|
832
|
+
|| !isSha256Digest(evidenceArg.workloadInitExecutableDigest)) {
|
|
833
|
+
errors.push(`secret runtime nodeEvidence[${index}] digests must be canonical`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
errors.push(...validateRuntimeTargets(evidenceTargets, 'secret runtime evidence targets'));
|
|
837
|
+
if (JSON.stringify(evidenceTargets) !== JSON.stringify(registrationArg.targets)) {
|
|
838
|
+
errors.push('secret runtime node evidence must exactly cover registration targets in order');
|
|
839
|
+
}
|
|
840
|
+
const expectedArtifacts = new Map<
|
|
841
|
+
TImmutableContainerPlatform,
|
|
842
|
+
ICoreflowSecretRuntimeArtifactV1
|
|
843
|
+
>((expectationArg.workloadInitArtifacts as ICoreflowSecretRuntimeArtifactV1[])
|
|
844
|
+
.map((artifactArg) => [artifactArg.platform, artifactArg]));
|
|
845
|
+
for (const [index, artifactArg] of evidenceArtifacts.entries()) {
|
|
846
|
+
const expectedArtifact = expectedArtifacts.get(artifactArg.platform);
|
|
847
|
+
if (!expectedArtifact
|
|
848
|
+
|| expectedArtifact.workloadInitPlatformManifestDigest
|
|
849
|
+
!== artifactArg.workloadInitPlatformManifestDigest
|
|
850
|
+
|| expectedArtifact.workloadInitExecutableDigest
|
|
851
|
+
!== artifactArg.workloadInitExecutableDigest) {
|
|
852
|
+
errors.push(`secret runtime nodeEvidence[${index}] does not match the approved artifact`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (JSON.stringify(registrationArg.targets) !== JSON.stringify(expectationArg.targets)) {
|
|
857
|
+
errors.push('secret runtime registration must exactly cover authoritative target nodes');
|
|
858
|
+
}
|
|
859
|
+
if (expectedTargetErrors.length === 0 && registrationTargetErrors.length === 0) {
|
|
860
|
+
const expectedNodeSetDigest = await computeCoreflowSecretRuntimeNodeSetDigest(
|
|
861
|
+
expectationArg.targets as ICoreflowSecretRuntimeNodeTargetV1[],
|
|
862
|
+
);
|
|
863
|
+
if (registrationArg.nodeSetDigest !== expectedNodeSetDigest) {
|
|
864
|
+
errors.push('secret runtime nodeSetDigest does not match authoritative target nodes');
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (registrationArg.workloadInitImageIndexDigest
|
|
868
|
+
!== expectationArg.workloadInitImageIndexDigest) {
|
|
869
|
+
errors.push('secret runtime WorkloadInit image index is not approved');
|
|
870
|
+
}
|
|
871
|
+
if (registrationRecipientErrors.length === 0 && expectedRecipientErrors.length === 0
|
|
872
|
+
&& activeRecipientFingerprint(
|
|
873
|
+
registrationArg.activeRecipient as unknown as IActiveSecretRecipientMetadata,
|
|
874
|
+
) !== activeRecipientFingerprint(
|
|
875
|
+
expectationArg.activeRecipient as unknown as IActiveSecretRecipientMetadata,
|
|
876
|
+
)) {
|
|
877
|
+
errors.push('secret runtime registration recipient does not match the active recipient');
|
|
878
|
+
}
|
|
879
|
+
return errors;
|
|
880
|
+
} catch {
|
|
881
|
+
return ['secret runtime registration and expectation must be safely inspectable'];
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
interface ISecretDeploymentReportBaseV1 {
|
|
886
|
+
schemaVersion: 1;
|
|
887
|
+
reporterSessionId: string;
|
|
888
|
+
reportSequence: number;
|
|
889
|
+
serviceId: string;
|
|
890
|
+
expectedPlanRevision: number;
|
|
891
|
+
desiredManifest: IResolvedSecretManifestReference;
|
|
892
|
+
reportedAt: number;
|
|
893
|
+
reportDigest: TSha256Digest;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
export interface ISecretDeploymentApplyingReportV1 extends ISecretDeploymentReportBaseV1 {
|
|
897
|
+
status: 'applying';
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
export interface ISecretDeploymentAppliedReportV1 extends ISecretDeploymentReportBaseV1 {
|
|
901
|
+
status: 'applied';
|
|
902
|
+
appliedManifest: IResolvedSecretManifestReference;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
export interface ISecretDeploymentDriftedReportV1 extends ISecretDeploymentReportBaseV1 {
|
|
906
|
+
status: 'drifted';
|
|
907
|
+
observedManifest?: IResolvedSecretManifestReference;
|
|
908
|
+
failureCode: string;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
export interface ISecretDeploymentFailedReportV1 extends ISecretDeploymentReportBaseV1 {
|
|
912
|
+
status: 'failed';
|
|
913
|
+
failureCode: string;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
export type TSecretDeploymentReportV1 =
|
|
917
|
+
| ISecretDeploymentApplyingReportV1
|
|
918
|
+
| ISecretDeploymentAppliedReportV1
|
|
919
|
+
| ISecretDeploymentDriftedReportV1
|
|
920
|
+
| ISecretDeploymentFailedReportV1;
|
|
921
|
+
|
|
922
|
+
export type TSecretDeploymentReportWithoutDigestV1 =
|
|
923
|
+
| Omit<ISecretDeploymentApplyingReportV1, 'reportDigest'>
|
|
924
|
+
| Omit<ISecretDeploymentAppliedReportV1, 'reportDigest'>
|
|
925
|
+
| Omit<ISecretDeploymentDriftedReportV1, 'reportDigest'>
|
|
926
|
+
| Omit<ISecretDeploymentFailedReportV1, 'reportDigest'>;
|
|
927
|
+
|
|
928
|
+
export interface ISecretDeploymentReportResponseV1 {
|
|
929
|
+
schemaVersion: 1;
|
|
930
|
+
acceptedSequence: number;
|
|
931
|
+
planRevision: number;
|
|
932
|
+
clusterState: IClusterSecretDeploymentState;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
export interface IReq_ReportSecretDeploymentState
|
|
936
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
937
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
938
|
+
IReq_ReportSecretDeploymentState
|
|
939
|
+
> {
|
|
940
|
+
method: 'reportSecretDeploymentState';
|
|
941
|
+
request: TSecretDeploymentReportV1 & {
|
|
942
|
+
identity: IIdentityCredential;
|
|
943
|
+
};
|
|
944
|
+
response: ISecretDeploymentReportResponseV1;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const canonicalManifestReference = (
|
|
948
|
+
referenceArg: IResolvedSecretManifestReference,
|
|
949
|
+
): IResolvedSecretManifestReference => ({
|
|
950
|
+
manifestId: referenceArg.manifestId,
|
|
951
|
+
manifestDigest: referenceArg.manifestDigest,
|
|
952
|
+
clusterId: referenceArg.clusterId,
|
|
953
|
+
imageRolloutId: referenceArg.imageRolloutId,
|
|
954
|
+
imageRolloutGeneration: referenceArg.imageRolloutGeneration,
|
|
955
|
+
requestedImageDigest: referenceArg.requestedImageDigest,
|
|
956
|
+
invocationDigest: referenceArg.invocationDigest,
|
|
957
|
+
secretRolloutId: referenceArg.secretRolloutId,
|
|
958
|
+
secretRolloutGeneration: referenceArg.secretRolloutGeneration,
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
export const createSecretDeploymentReportDigestInput = (
|
|
962
|
+
reportArg: TSecretDeploymentReportWithoutDigestV1 | TSecretDeploymentReportV1,
|
|
963
|
+
): string => {
|
|
964
|
+
const common = {
|
|
965
|
+
schemaVersion: reportArg.schemaVersion,
|
|
966
|
+
reporterSessionId: reportArg.reporterSessionId,
|
|
967
|
+
reportSequence: reportArg.reportSequence,
|
|
968
|
+
serviceId: reportArg.serviceId,
|
|
969
|
+
expectedPlanRevision: reportArg.expectedPlanRevision,
|
|
970
|
+
desiredManifest: canonicalManifestReference(reportArg.desiredManifest),
|
|
971
|
+
status: reportArg.status,
|
|
972
|
+
};
|
|
973
|
+
if (reportArg.status === 'applied') {
|
|
974
|
+
return JSON.stringify({
|
|
975
|
+
...common,
|
|
976
|
+
appliedManifest: canonicalManifestReference(reportArg.appliedManifest),
|
|
977
|
+
reportedAt: reportArg.reportedAt,
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
if (reportArg.status === 'drifted') {
|
|
981
|
+
return JSON.stringify({
|
|
982
|
+
...common,
|
|
983
|
+
...(reportArg.observedManifest
|
|
984
|
+
? { observedManifest: canonicalManifestReference(reportArg.observedManifest) }
|
|
985
|
+
: {}),
|
|
986
|
+
failureCode: reportArg.failureCode,
|
|
987
|
+
reportedAt: reportArg.reportedAt,
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
if (reportArg.status === 'failed') {
|
|
991
|
+
return JSON.stringify({
|
|
992
|
+
...common,
|
|
993
|
+
failureCode: reportArg.failureCode,
|
|
994
|
+
reportedAt: reportArg.reportedAt,
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
return JSON.stringify({ ...common, reportedAt: reportArg.reportedAt });
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
export const computeSecretDeploymentReportDigest = async (
|
|
1001
|
+
reportArg: TSecretDeploymentReportWithoutDigestV1 | TSecretDeploymentReportV1,
|
|
1002
|
+
): Promise<TSha256Digest> => computeRuntimeSha256(
|
|
1003
|
+
createSecretDeploymentReportDigestInput(reportArg),
|
|
1004
|
+
);
|
|
1005
|
+
|
|
1006
|
+
export const verifySecretDeploymentReportDigest = async (
|
|
1007
|
+
reportArg: unknown,
|
|
1008
|
+
): Promise<boolean> => {
|
|
1009
|
+
try {
|
|
1010
|
+
if (!isRuntimeRecord(reportArg)
|
|
1011
|
+
|| typeof reportArg.reportDigest !== 'string'
|
|
1012
|
+
|| !isSha256Digest(reportArg.reportDigest)) {
|
|
1013
|
+
return false;
|
|
1014
|
+
}
|
|
1015
|
+
return reportArg.reportDigest === await computeSecretDeploymentReportDigest(
|
|
1016
|
+
reportArg as unknown as TSecretDeploymentReportV1,
|
|
1017
|
+
);
|
|
1018
|
+
} catch {
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* The expected cluster and reporter session come from the consumer's verified
|
|
1025
|
+
* live connection authority and are deliberately not accepted from the wire.
|
|
1026
|
+
*/
|
|
1027
|
+
export const validateSecretDeploymentReportRequest = async (
|
|
1028
|
+
requestArg: unknown,
|
|
1029
|
+
expectedClusterIdArg: string,
|
|
1030
|
+
expectedReporterSessionIdArg: string,
|
|
1031
|
+
): Promise<string[]> => {
|
|
1032
|
+
try {
|
|
1033
|
+
if (!isRuntimeIdentifier(expectedClusterIdArg)) {
|
|
1034
|
+
return ['trusted expected cluster id must be canonical'];
|
|
1035
|
+
}
|
|
1036
|
+
if (!isRuntimeIdentifier(expectedReporterSessionIdArg)) {
|
|
1037
|
+
return ['trusted expected reporter session id must be canonical'];
|
|
1038
|
+
}
|
|
1039
|
+
if (!isRuntimeRecord(requestArg)) {
|
|
1040
|
+
return ['secret deployment report request must be an object'];
|
|
1041
|
+
}
|
|
1042
|
+
const status = requestArg.status;
|
|
1043
|
+
const commonKeys = [
|
|
1044
|
+
'identity',
|
|
1045
|
+
'schemaVersion',
|
|
1046
|
+
'reporterSessionId',
|
|
1047
|
+
'reportSequence',
|
|
1048
|
+
'serviceId',
|
|
1049
|
+
'expectedPlanRevision',
|
|
1050
|
+
'desiredManifest',
|
|
1051
|
+
'status',
|
|
1052
|
+
'reportedAt',
|
|
1053
|
+
'reportDigest',
|
|
1054
|
+
];
|
|
1055
|
+
const expectedKeys = status === 'applied'
|
|
1056
|
+
? [...commonKeys, 'appliedManifest']
|
|
1057
|
+
: status === 'drifted'
|
|
1058
|
+
? [...commonKeys, 'failureCode', ...(requestArg.observedManifest === undefined
|
|
1059
|
+
? []
|
|
1060
|
+
: ['observedManifest'])]
|
|
1061
|
+
: status === 'failed'
|
|
1062
|
+
? [...commonKeys, 'failureCode']
|
|
1063
|
+
: commonKeys;
|
|
1064
|
+
const errors: string[] = [];
|
|
1065
|
+
if (!['applying', 'applied', 'drifted', 'failed'].includes(status as string)
|
|
1066
|
+
|| !hasExactRuntimeKeys(requestArg, expectedKeys)) {
|
|
1067
|
+
errors.push('secret deployment report must use its exact status schema');
|
|
1068
|
+
}
|
|
1069
|
+
if (!isRuntimeRecord(requestArg.identity)
|
|
1070
|
+
|| !hasExactRuntimeKeys(requestArg.identity, ['jwt'])
|
|
1071
|
+
|| typeof requestArg.identity.jwt !== 'string'
|
|
1072
|
+
|| requestArg.identity.jwt.length === 0) {
|
|
1073
|
+
errors.push('secret deployment report requires a JWT identity');
|
|
1074
|
+
}
|
|
1075
|
+
if (requestArg.schemaVersion !== 1) {
|
|
1076
|
+
errors.push('secret deployment report schemaVersion must be 1');
|
|
1077
|
+
}
|
|
1078
|
+
if (!isRuntimeIdentifier(requestArg.reporterSessionId)
|
|
1079
|
+
|| !isRuntimeIdentifier(requestArg.serviceId)) {
|
|
1080
|
+
errors.push('secret deployment report identity fields must be canonical');
|
|
1081
|
+
} else if (requestArg.reporterSessionId !== expectedReporterSessionIdArg) {
|
|
1082
|
+
errors.push('secret deployment report reporterSessionId does not match the live session');
|
|
1083
|
+
}
|
|
1084
|
+
if (!isPositiveSafeInteger(requestArg.reportSequence)
|
|
1085
|
+
|| !isPositiveSafeInteger(requestArg.expectedPlanRevision)
|
|
1086
|
+
|| !isPositiveSafeInteger(requestArg.reportedAt)) {
|
|
1087
|
+
errors.push('secret deployment report revisions, sequence, and timestamp must be positive integers');
|
|
1088
|
+
}
|
|
1089
|
+
errors.push(...validateResolvedSecretManifestReference(
|
|
1090
|
+
requestArg.desiredManifest,
|
|
1091
|
+
expectedClusterIdArg,
|
|
1092
|
+
).map((errorArg) => `desiredManifest: ${errorArg}`));
|
|
1093
|
+
if (status === 'applied') {
|
|
1094
|
+
errors.push(...validateResolvedSecretManifestReference(
|
|
1095
|
+
requestArg.appliedManifest,
|
|
1096
|
+
expectedClusterIdArg,
|
|
1097
|
+
).map((errorArg) => `appliedManifest: ${errorArg}`));
|
|
1098
|
+
if (!resolvedSecretManifestReferencesEqual(
|
|
1099
|
+
requestArg.appliedManifest as IResolvedSecretManifestReference,
|
|
1100
|
+
requestArg.desiredManifest as IResolvedSecretManifestReference,
|
|
1101
|
+
)) {
|
|
1102
|
+
errors.push('applied secret deployment report must apply the desired manifest');
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
if (status === 'drifted' && requestArg.observedManifest !== undefined) {
|
|
1106
|
+
errors.push(...validateResolvedSecretManifestReference(
|
|
1107
|
+
requestArg.observedManifest,
|
|
1108
|
+
expectedClusterIdArg,
|
|
1109
|
+
).map((errorArg) => `observedManifest: ${errorArg}`));
|
|
1110
|
+
}
|
|
1111
|
+
if ((status === 'drifted' || status === 'failed')
|
|
1112
|
+
&& (typeof requestArg.failureCode !== 'string'
|
|
1113
|
+
|| !runtimeFailureCodeRegex.test(requestArg.failureCode))) {
|
|
1114
|
+
errors.push('secret deployment report failureCode must be canonical');
|
|
1115
|
+
}
|
|
1116
|
+
if (typeof requestArg.reportDigest !== 'string'
|
|
1117
|
+
|| !isSha256Digest(requestArg.reportDigest)
|
|
1118
|
+
|| !await verifySecretDeploymentReportDigest(requestArg)) {
|
|
1119
|
+
errors.push('secret deployment report digest does not match its canonical payload');
|
|
1120
|
+
}
|
|
1121
|
+
return errors;
|
|
1122
|
+
} catch {
|
|
1123
|
+
return ['secret deployment report request must be safely inspectable'];
|
|
1124
|
+
}
|
|
1125
|
+
};
|