mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 18:08:20 +08:00
1507 lines
48 KiB
TypeScript
1507 lines
48 KiB
TypeScript
/** Shared one-shot authenticated client boundary for cluster management planes. */
|
|
import { Agent as HttpsAgent, request as httpsRequest } from 'node:https';
|
|
import { Duplex } from 'node:stream';
|
|
import { connect as tlsConnect } from 'node:tls';
|
|
import { TextDecoder } from 'node:util';
|
|
import { parseSecretRef } from '@qinglong/runtime-core/secret-reference';
|
|
import {
|
|
ClusterPluginPackageManagementClientConfigurationError,
|
|
isReviewedClusterAuthenticatedManagementClientProtocol,
|
|
prepareClusterAuthenticatedManagementClientConfiguration,
|
|
readCanonicalFile,
|
|
validateClusterAuthenticatedManagementClientConfiguration,
|
|
type ClusterAuthenticatedManagementClientConfigurationSummary,
|
|
type ClusterAuthenticatedManagementClientKind,
|
|
type PreparedClusterAuthenticatedManagementClientConfiguration,
|
|
} from './managementClientConfiguration';
|
|
|
|
export {
|
|
ClusterPluginPackageManagementClientConfigurationError,
|
|
readCanonicalFile,
|
|
validateClusterAuthenticatedManagementClientConfiguration,
|
|
};
|
|
export type {
|
|
ClusterAuthenticatedManagementClientConfigurationSummary,
|
|
ClusterAuthenticatedManagementClientKind,
|
|
};
|
|
|
|
import {
|
|
normalizeClusterPluginPackageManagementCommand,
|
|
type ClusterPluginPackageManagementCommand,
|
|
type ClusterPluginPackageManagementTransportResult,
|
|
} from '../plugin-package/management/pluginPackageManagementTransport';
|
|
|
|
const MANAGEMENT_PATH = '/api/v3/plugin-packages/management';
|
|
const MAX_ASSERTION_BYTES = 16 * 1024;
|
|
const MAX_COMMAND_BYTES = 256 * 1024;
|
|
const MAX_RESPONSE_BYTES = 128 * 1024;
|
|
const CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
|
|
const PACKAGE_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
const ASSERTION_PATTERN = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
const TOKEN_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
|
|
const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
|
|
|
|
type JsonObject = Record<string, unknown>;
|
|
|
|
export interface ClusterPluginPackageManagementClientPaths {
|
|
readonly configFile: string;
|
|
readonly commandFile: string;
|
|
readonly assertionFile: string;
|
|
}
|
|
|
|
export interface ClusterAuthenticatedManagementCommandExecution<Command> {
|
|
readonly configFile: string;
|
|
readonly assertionFile: string;
|
|
readonly command: Command;
|
|
}
|
|
|
|
export interface ClusterPluginPackageManagementClientResult {
|
|
readonly schemaVersion: 1;
|
|
readonly requestId: string;
|
|
readonly result: Readonly<ClusterPluginPackageManagementTransportResult>;
|
|
}
|
|
|
|
export interface ClusterAuthenticatedManagementClientResult<Result> {
|
|
readonly schemaVersion: 1;
|
|
readonly requestId: string;
|
|
readonly result: Readonly<Result>;
|
|
}
|
|
|
|
export interface ClusterAuthenticatedManagementClientProtocol<Command, Result> {
|
|
readonly managementPath: string;
|
|
readonly clientCertificate: 'forbidden' | 'required';
|
|
normalizeCommand(value: unknown): Readonly<Command>;
|
|
validateResult(value: unknown, command: Readonly<Command>): Readonly<Result>;
|
|
}
|
|
|
|
export interface ClusterPluginPackageManagementClientConnectionTarget {
|
|
readonly hostname: string;
|
|
readonly port: number;
|
|
}
|
|
|
|
export interface ClusterPluginPackageManagementClientRawConnection {
|
|
readonly stream: Duplex;
|
|
close(): void | Promise<void>;
|
|
}
|
|
|
|
export interface ClusterPluginPackageManagementClientConnectionOptions {
|
|
connect(
|
|
target: Readonly<ClusterPluginPackageManagementClientConnectionTarget>,
|
|
): Promise<ClusterPluginPackageManagementClientRawConnection>;
|
|
}
|
|
|
|
export class ClusterPluginPackageManagementClientRequestError extends Error {
|
|
readonly code = 'QL3_PLUGIN_PACKAGE_MANAGEMENT_CLIENT_REQUEST_FAILED';
|
|
|
|
constructor(readonly cause?: unknown) {
|
|
super('Plugin Package management client request failed');
|
|
this.name = 'ClusterPluginPackageManagementClientRequestError';
|
|
}
|
|
}
|
|
|
|
export class ClusterPluginPackageManagementClientRemoteError extends Error {
|
|
readonly code = 'QL3_PLUGIN_PACKAGE_MANAGEMENT_CLIENT_REMOTE_REJECTED';
|
|
|
|
constructor(
|
|
readonly statusCode: number,
|
|
readonly responseCode: string,
|
|
readonly requestId: string,
|
|
readonly retryAfterSeconds: number | null,
|
|
) {
|
|
super('Plugin Package management server rejected the request');
|
|
this.name = 'ClusterPluginPackageManagementClientRemoteError';
|
|
}
|
|
}
|
|
|
|
function configurationFailure(): ClusterPluginPackageManagementClientConfigurationError {
|
|
return new ClusterPluginPackageManagementClientConfigurationError();
|
|
}
|
|
|
|
function exactObject(
|
|
value: unknown,
|
|
expectedKeys: readonly string[],
|
|
): asserts value is JsonObject {
|
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
throw configurationFailure();
|
|
}
|
|
const actual = Object.keys(value).sort();
|
|
const expected = [...expectedKeys].sort();
|
|
if (
|
|
actual.length !== expected.length ||
|
|
actual.some((key, index) => key !== expected[index])
|
|
) {
|
|
throw configurationFailure();
|
|
}
|
|
}
|
|
|
|
function decodeUtf8(bytes: Buffer): string {
|
|
try {
|
|
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
} catch {
|
|
throw configurationFailure();
|
|
}
|
|
}
|
|
|
|
function parseJson(bytes: Buffer): unknown {
|
|
try {
|
|
return JSON.parse(decodeUtf8(bytes));
|
|
} catch (error) {
|
|
if (
|
|
error instanceof ClusterPluginPackageManagementClientConfigurationError
|
|
) {
|
|
throw error;
|
|
}
|
|
throw configurationFailure();
|
|
}
|
|
}
|
|
|
|
function boundedString(value: unknown, maximum = 1_024): string {
|
|
if (
|
|
typeof value !== 'string' ||
|
|
value.length < 1 ||
|
|
value.length > maximum ||
|
|
CONTROL_PATTERN.test(value)
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function exactResponseObject(
|
|
value: unknown,
|
|
expectedKeys: readonly string[],
|
|
): JsonObject {
|
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
const actual = Object.keys(value).sort();
|
|
const expected = [...expectedKeys].sort();
|
|
if (
|
|
actual.length !== expected.length ||
|
|
actual.some((key, index) => key !== expected[index])
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
return value as JsonObject;
|
|
}
|
|
|
|
const APPROVAL_KEYS = Object.freeze([
|
|
'id',
|
|
'projectId',
|
|
'version',
|
|
'state',
|
|
'risk',
|
|
'decisionMode',
|
|
'requestedAtMs',
|
|
'expiresAtMs',
|
|
'decision',
|
|
'decisionReasonCode',
|
|
'decidedAtMs',
|
|
'dispatchId',
|
|
'consumedAtMs',
|
|
'actionDigest',
|
|
'previewDigest',
|
|
]);
|
|
const INSTALL_PROPOSAL_KEYS = Object.freeze([
|
|
'actionRef',
|
|
'projectId',
|
|
'packageName',
|
|
'packageVersion',
|
|
'operation',
|
|
'sourceKind',
|
|
'architecture',
|
|
'deploymentProfile',
|
|
'targetGeneration',
|
|
'actionDigest',
|
|
'previewDigest',
|
|
'proposalDigest',
|
|
'createdAtMs',
|
|
]);
|
|
const INSTALLATION_SUMMARY_KEYS = Object.freeze([
|
|
'installationId',
|
|
'projectId',
|
|
'packageName',
|
|
'packageVersion',
|
|
'operation',
|
|
'state',
|
|
'targetGeneration',
|
|
'activeLockDigest',
|
|
'previousActiveLockDigest',
|
|
'recoveryAction',
|
|
'availability',
|
|
'quarantineReason',
|
|
'quarantineAuthorizationMode',
|
|
'quarantineEventDigest',
|
|
'quarantinedAtMs',
|
|
'withdrawalStatus',
|
|
'withdrawalReceiptDigest',
|
|
'withdrawalCommittedAtMs',
|
|
'failureReason',
|
|
'failedFrom',
|
|
'failedAtMs',
|
|
'version',
|
|
'createdAtMs',
|
|
'updatedAtMs',
|
|
'recordDigest',
|
|
]);
|
|
const REVOCATION_PROPOSAL_KEYS = Object.freeze([
|
|
'actionRef',
|
|
'projectId',
|
|
'trustAuthorityId',
|
|
'trustGeneration',
|
|
'publisher',
|
|
'keyId',
|
|
'previousTrustDigest',
|
|
'currentTrustDigest',
|
|
'authorizationMode',
|
|
'reasonCode',
|
|
'actionDigest',
|
|
'previewDigest',
|
|
'proposalDigest',
|
|
'createdAtMs',
|
|
]);
|
|
const TRANSITION_PROPOSAL_KEYS = Object.freeze([
|
|
'actionRef',
|
|
'projectId',
|
|
'trustAuthorityId',
|
|
'trustGeneration',
|
|
'mode',
|
|
'publisher',
|
|
'keyId',
|
|
'previousTrustDigest',
|
|
'currentTrustDigest',
|
|
'actionDigest',
|
|
'previewDigest',
|
|
'proposalDigest',
|
|
'createdAtMs',
|
|
]);
|
|
const LIFECYCLE_PLAN_KEYS = Object.freeze([
|
|
'actionRef',
|
|
'planDigest',
|
|
'plannedAtMs',
|
|
'expiresAtMs',
|
|
'action',
|
|
'projectId',
|
|
'packageName',
|
|
'installationId',
|
|
'lockDigest',
|
|
'installVersion',
|
|
'installRecordDigest',
|
|
'expected',
|
|
'generationDigest',
|
|
'materializedRevisionDigest',
|
|
'currentToolSnapshotDigest',
|
|
'taskIds',
|
|
'resourceCounts',
|
|
'referenceGraphDigest',
|
|
'blockingReferences',
|
|
'impactDigest',
|
|
]);
|
|
const SECRET_BINDING_PLAN_KEYS = Object.freeze([
|
|
'actionRef',
|
|
'projectId',
|
|
'packageName',
|
|
'installationId',
|
|
'generation',
|
|
'generationDigest',
|
|
'lockDigest',
|
|
'manifestDigest',
|
|
'entries',
|
|
'plannedAtMs',
|
|
'expiresAtMs',
|
|
'planDigest',
|
|
'approvalPlanDigest',
|
|
]);
|
|
const SECRET_BINDING_TRANSITION_PLAN_KEYS = Object.freeze([
|
|
'actionRef',
|
|
'approvalPlanDigest',
|
|
'plannedAtMs',
|
|
'expiresAtMs',
|
|
'kind',
|
|
'transitionDigest',
|
|
'projectId',
|
|
'packageName',
|
|
'previousInstallationId',
|
|
'previousGeneration',
|
|
'previousGenerationDigest',
|
|
'previousActiveLockDigest',
|
|
'previousAttemptGeneration',
|
|
'nextInstallationId',
|
|
'nextGeneration',
|
|
'nextGenerationDigest',
|
|
'nextLockDigest',
|
|
'nextManifestDigest',
|
|
'changes',
|
|
]);
|
|
|
|
function validateScalarSummary(value: unknown, keys: readonly string[]): void {
|
|
const record = exactResponseObject(value, keys);
|
|
for (const entry of Object.values(record)) {
|
|
if (
|
|
entry !== null &&
|
|
!(
|
|
typeof entry === 'string' &&
|
|
entry.length <= 2_048 &&
|
|
!CONTROL_PATTERN.test(entry)
|
|
) &&
|
|
!(typeof entry === 'number' && Number.isSafeInteger(entry))
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
}
|
|
}
|
|
|
|
function validateInstallationSummary(
|
|
value: unknown,
|
|
projectId: string,
|
|
packageName?: string,
|
|
): JsonObject {
|
|
validateScalarSummary(value, INSTALLATION_SUMMARY_KEYS);
|
|
const summary = value as JsonObject;
|
|
if (
|
|
summary.projectId !== projectId ||
|
|
typeof summary.packageName !== 'string' ||
|
|
!PACKAGE_NAME_PATTERN.test(summary.packageName) ||
|
|
(packageName !== undefined && summary.packageName !== packageName) ||
|
|
!['queued', 'staged', 'activating', 'active', 'failed'].includes(
|
|
String(summary.state),
|
|
) ||
|
|
!['active', 'not_active', 'quarantined'].includes(
|
|
String(summary.availability),
|
|
)
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
const quarantineValues = [
|
|
summary.quarantineReason,
|
|
summary.quarantineAuthorizationMode,
|
|
summary.quarantineEventDigest,
|
|
summary.quarantinedAtMs,
|
|
summary.withdrawalStatus,
|
|
summary.withdrawalReceiptDigest,
|
|
summary.withdrawalCommittedAtMs,
|
|
];
|
|
if (
|
|
summary.availability === 'quarantined'
|
|
? quarantineValues.some((entry) => entry === null)
|
|
: quarantineValues.some((entry) => entry !== null) ||
|
|
summary.availability !==
|
|
(summary.state === 'active' ? 'active' : 'not_active')
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
const failureValues = [
|
|
summary.failureReason,
|
|
summary.failedFrom,
|
|
summary.failedAtMs,
|
|
];
|
|
if (
|
|
summary.state === 'failed'
|
|
? failureValues.some((entry) => entry === null)
|
|
: failureValues.some((entry) => entry !== null)
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
return summary;
|
|
}
|
|
|
|
function validateLifecyclePlanSummary(value: unknown): void {
|
|
const summary = exactResponseObject(value, LIFECYCLE_PLAN_KEYS);
|
|
const expected = exactResponseObject(summary.expected, [
|
|
'version',
|
|
'disposition',
|
|
'eventDigest',
|
|
]);
|
|
const resourceCounts = exactResponseObject(summary.resourceCounts, [
|
|
'tasks',
|
|
'tools',
|
|
'workflows',
|
|
'prompts',
|
|
]);
|
|
if (
|
|
typeof summary.actionRef !== 'string' ||
|
|
summary.actionRef.length < 1 ||
|
|
summary.actionRef.length > 255 ||
|
|
!['disable', 'enable', 'uninstall'].includes(String(summary.action)) ||
|
|
typeof summary.projectId !== 'string' ||
|
|
summary.projectId.length < 1 ||
|
|
summary.projectId.length > 128 ||
|
|
typeof summary.packageName !== 'string' ||
|
|
!PACKAGE_NAME_PATTERN.test(summary.packageName) ||
|
|
typeof summary.installationId !== 'string' ||
|
|
summary.installationId.length < 1 ||
|
|
summary.installationId.length > 128 ||
|
|
!Number.isSafeInteger(summary.plannedAtMs) ||
|
|
!Number.isSafeInteger(summary.expiresAtMs) ||
|
|
(summary.expiresAtMs as number) <= (summary.plannedAtMs as number) ||
|
|
!Number.isSafeInteger(summary.installVersion) ||
|
|
!Number.isSafeInteger(expected.version) ||
|
|
!['active', 'disabled', 'uninstalled'].includes(
|
|
String(expected.disposition),
|
|
) ||
|
|
(expected.eventDigest !== null &&
|
|
(typeof expected.eventDigest !== 'string' ||
|
|
!DIGEST_PATTERN.test(expected.eventDigest))) ||
|
|
Object.values(resourceCounts).some(
|
|
(count) =>
|
|
!Number.isSafeInteger(count) ||
|
|
(count as number) < 0 ||
|
|
(count as number) > 256,
|
|
) ||
|
|
!Array.isArray(summary.taskIds) ||
|
|
summary.taskIds.length > 128 ||
|
|
summary.taskIds.some(
|
|
(taskId) =>
|
|
typeof taskId !== 'string' ||
|
|
taskId.length < 1 ||
|
|
taskId.length > 128 ||
|
|
CONTROL_PATTERN.test(taskId),
|
|
) ||
|
|
!Array.isArray(summary.blockingReferences) ||
|
|
summary.blockingReferences.length > 128
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
for (const key of [
|
|
'planDigest',
|
|
'lockDigest',
|
|
'installRecordDigest',
|
|
'generationDigest',
|
|
'materializedRevisionDigest',
|
|
'currentToolSnapshotDigest',
|
|
'referenceGraphDigest',
|
|
'impactDigest',
|
|
]) {
|
|
if (
|
|
typeof summary[key] !== 'string' ||
|
|
!DIGEST_PATTERN.test(summary[key] as string)
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
}
|
|
for (const reference of summary.blockingReferences) {
|
|
const item = exactResponseObject(reference, [
|
|
'kind',
|
|
'ownerId',
|
|
'referenceDigest',
|
|
]);
|
|
if (
|
|
![
|
|
'execution_recovery',
|
|
'prompt',
|
|
'publication_recovery',
|
|
'secret_binding',
|
|
'tool',
|
|
'workflow',
|
|
].includes(String(item.kind)) ||
|
|
typeof item.ownerId !== 'string' ||
|
|
item.ownerId.length < 1 ||
|
|
item.ownerId.length > 128 ||
|
|
CONTROL_PATTERN.test(item.ownerId) ||
|
|
typeof item.referenceDigest !== 'string' ||
|
|
!DIGEST_PATTERN.test(item.referenceDigest)
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
}
|
|
}
|
|
|
|
function validateSecretBindingPlanSummary(
|
|
value: unknown,
|
|
command: Readonly<
|
|
Extract<
|
|
ClusterPluginPackageManagementCommand,
|
|
{ readonly operation: `plugin-package.secret-binding.${string}` }
|
|
>
|
|
>,
|
|
): void {
|
|
const summary = exactResponseObject(value, SECRET_BINDING_PLAN_KEYS);
|
|
if (
|
|
typeof summary.actionRef !== 'string' ||
|
|
summary.actionRef.length < 1 ||
|
|
summary.actionRef.length > 255 ||
|
|
typeof summary.projectId !== 'string' ||
|
|
summary.projectId.length < 1 ||
|
|
summary.projectId.length > 128 ||
|
|
typeof summary.packageName !== 'string' ||
|
|
!PACKAGE_NAME_PATTERN.test(summary.packageName) ||
|
|
typeof summary.installationId !== 'string' ||
|
|
summary.installationId.length < 1 ||
|
|
summary.installationId.length > 128 ||
|
|
!Number.isSafeInteger(summary.generation) ||
|
|
(summary.generation as number) < 1 ||
|
|
!Number.isSafeInteger(summary.plannedAtMs) ||
|
|
!Number.isSafeInteger(summary.expiresAtMs) ||
|
|
(summary.expiresAtMs as number) <= (summary.plannedAtMs as number) ||
|
|
!Array.isArray(summary.entries) ||
|
|
summary.entries.length > 64 ||
|
|
new Set(
|
|
summary.entries.map((entry) =>
|
|
entry && typeof entry === 'object' && !Array.isArray(entry)
|
|
? (entry as JsonObject).name
|
|
: undefined,
|
|
),
|
|
).size !== summary.entries.length
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
for (const key of [
|
|
'generationDigest',
|
|
'lockDigest',
|
|
'manifestDigest',
|
|
'planDigest',
|
|
'approvalPlanDigest',
|
|
]) {
|
|
if (
|
|
typeof summary[key] !== 'string' ||
|
|
!DIGEST_PATTERN.test(summary[key] as string)
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
}
|
|
for (const entryValue of summary.entries) {
|
|
const entry = exactResponseObject(entryValue, [
|
|
'name',
|
|
'required',
|
|
'secretRef',
|
|
]);
|
|
if (
|
|
typeof entry.name !== 'string' ||
|
|
!/^[A-Z_][A-Z0-9_]{0,127}$/.test(entry.name) ||
|
|
typeof entry.required !== 'boolean' ||
|
|
(entry.secretRef !== null &&
|
|
(typeof entry.secretRef !== 'string' ||
|
|
entry.secretRef.length > 2_048 ||
|
|
CONTROL_PATTERN.test(entry.secretRef))) ||
|
|
(entry.required === true && entry.secretRef === null)
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
if (entry.secretRef !== null) {
|
|
try {
|
|
const reference = parseSecretRef(entry.secretRef);
|
|
if (
|
|
reference.projectId !== summary.projectId ||
|
|
typeof reference.version !== 'number' ||
|
|
!Number.isSafeInteger(reference.version) ||
|
|
reference.version < 1
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof ClusterPluginPackageManagementClientRequestError) {
|
|
throw error;
|
|
}
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
}
|
|
}
|
|
if (
|
|
summary.actionRef !== command.request.actionRef ||
|
|
(command.operation === 'plugin-package.secret-binding.plan' &&
|
|
(summary.projectId !== command.request.projectId ||
|
|
summary.packageName !== command.request.packageName ||
|
|
summary.entries.length !== command.request.assignments.length ||
|
|
command.request.assignments.some((assignment) => {
|
|
const responseEntry = (summary.entries as JsonObject[]).find(
|
|
(entry) => entry.name === assignment.name,
|
|
);
|
|
return (
|
|
!responseEntry || responseEntry.secretRef !== assignment.secretRef
|
|
);
|
|
})))
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
}
|
|
|
|
function validateSecretBindingTransitionPlanSummary(
|
|
value: unknown,
|
|
command: Readonly<ClusterPluginPackageManagementCommand>,
|
|
): void {
|
|
const summary = exactResponseObject(
|
|
value,
|
|
SECRET_BINDING_TRANSITION_PLAN_KEYS,
|
|
);
|
|
const transitionRequest = command.request as { readonly actionRef: string };
|
|
if (
|
|
typeof summary.actionRef !== 'string' ||
|
|
summary.actionRef !== transitionRequest.actionRef ||
|
|
typeof summary.projectId !== 'string' ||
|
|
typeof summary.packageName !== 'string' ||
|
|
!PACKAGE_NAME_PATTERN.test(summary.packageName) ||
|
|
typeof summary.previousInstallationId !== 'string' ||
|
|
typeof summary.nextInstallationId !== 'string' ||
|
|
!['carry-forward', 'rotate', 'rebind', 'revoke'].includes(
|
|
String(summary.kind),
|
|
) ||
|
|
!Number.isSafeInteger(summary.plannedAtMs) ||
|
|
!Number.isSafeInteger(summary.expiresAtMs) ||
|
|
(summary.expiresAtMs as number) <= (summary.plannedAtMs as number) ||
|
|
!Number.isSafeInteger(summary.previousGeneration) ||
|
|
!Number.isSafeInteger(summary.previousAttemptGeneration) ||
|
|
!Number.isSafeInteger(summary.nextGeneration) ||
|
|
(summary.previousGeneration as number) < 1 ||
|
|
(summary.previousAttemptGeneration as number) <
|
|
(summary.previousGeneration as number) ||
|
|
(summary.nextGeneration as number) !==
|
|
(summary.previousAttemptGeneration as number) + 1 ||
|
|
!Array.isArray(summary.changes) ||
|
|
summary.changes.length < 1 ||
|
|
summary.changes.length > 64
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
for (const key of [
|
|
'approvalPlanDigest',
|
|
'transitionDigest',
|
|
'previousGenerationDigest',
|
|
'previousActiveLockDigest',
|
|
'nextGenerationDigest',
|
|
'nextLockDigest',
|
|
'nextManifestDigest',
|
|
]) {
|
|
if (
|
|
typeof summary[key] !== 'string' ||
|
|
!DIGEST_PATTERN.test(summary[key] as string)
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
}
|
|
const nextEntries = new Map<string, string | null>();
|
|
const changedNames = new Set<string>();
|
|
for (const changeValue of summary.changes) {
|
|
const change = exactResponseObject(changeValue, [
|
|
'name',
|
|
'requirement',
|
|
'reference',
|
|
'previous',
|
|
'next',
|
|
]);
|
|
if (
|
|
typeof change.name !== 'string' ||
|
|
!/^[A-Z_][A-Z0-9_]{0,127}$/.test(change.name) ||
|
|
changedNames.has(change.name) ||
|
|
!['added', 'removed', 'tightened', 'relaxed', 'unchanged'].includes(
|
|
String(change.requirement),
|
|
) ||
|
|
!['bound', 'revoked', 'rotated', 'rebound', 'unchanged'].includes(
|
|
String(change.reference),
|
|
)
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
changedNames.add(change.name as string);
|
|
for (const stateValue of [change.previous, change.next]) {
|
|
if (stateValue === null) continue;
|
|
const state = exactResponseObject(stateValue, ['required', 'secretRef']);
|
|
if (
|
|
typeof state.required !== 'boolean' ||
|
|
(state.secretRef !== null && typeof state.secretRef !== 'string') ||
|
|
(state.required && state.secretRef === null)
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
if (state.secretRef !== null) {
|
|
try {
|
|
const reference = parseSecretRef(state.secretRef as string);
|
|
if (reference.projectId !== summary.projectId) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
} catch (error) {
|
|
if (
|
|
error instanceof ClusterPluginPackageManagementClientRequestError
|
|
) {
|
|
throw error;
|
|
}
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
}
|
|
}
|
|
if (change.next !== null) {
|
|
nextEntries.set(
|
|
change.name as string,
|
|
(change.next as JsonObject).secretRef as string | null,
|
|
);
|
|
}
|
|
}
|
|
if (
|
|
command.operation === 'plugin-package.secret-binding.transition.plan' &&
|
|
(summary.projectId !== command.request.projectId ||
|
|
summary.packageName !== command.request.packageName ||
|
|
nextEntries.size !== command.request.assignments.length ||
|
|
command.request.assignments.some(
|
|
(assignment) =>
|
|
nextEntries.get(assignment.name) !== assignment.secretRef,
|
|
))
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
}
|
|
|
|
function validateResult(
|
|
value: unknown,
|
|
command: Readonly<ClusterPluginPackageManagementCommand>,
|
|
): Readonly<ClusterPluginPackageManagementTransportResult> {
|
|
if (command.operation === 'plugin-package.secret-binding.transition.plan') {
|
|
const result = exactResponseObject(value, [
|
|
'schemaVersion',
|
|
'operation',
|
|
'status',
|
|
'plan',
|
|
]);
|
|
if (
|
|
result.schemaVersion !== 1 ||
|
|
result.operation !== command.operation ||
|
|
!['created', 'existing'].includes(String(result.status))
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
validateSecretBindingTransitionPlanSummary(result.plan, command);
|
|
return Object.freeze(
|
|
result as unknown as ClusterPluginPackageManagementTransportResult,
|
|
);
|
|
}
|
|
if (
|
|
command.operation === 'plugin-package.secret-binding.transition.propose'
|
|
) {
|
|
const result = exactResponseObject(value, [
|
|
'schemaVersion',
|
|
'operation',
|
|
'approvalStatus',
|
|
'plan',
|
|
'approval',
|
|
]);
|
|
if (
|
|
result.schemaVersion !== 1 ||
|
|
result.operation !== command.operation ||
|
|
!['created', 'existing'].includes(String(result.approvalStatus))
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
validateSecretBindingTransitionPlanSummary(result.plan, command);
|
|
validateScalarSummary(result.approval, APPROVAL_KEYS);
|
|
const plan = result.plan as JsonObject;
|
|
const approval = result.approval as JsonObject;
|
|
if (
|
|
approval.id !== command.request.approvalRequestId ||
|
|
approval.projectId !== plan.projectId ||
|
|
approval.actionDigest !== plan.approvalPlanDigest ||
|
|
approval.previewDigest !== plan.transitionDigest
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
return Object.freeze(
|
|
result as unknown as ClusterPluginPackageManagementTransportResult,
|
|
);
|
|
}
|
|
if (
|
|
command.operation === 'plugin-package.secret-binding.transition.inspect'
|
|
) {
|
|
const result = exactResponseObject(value, [
|
|
'schemaVersion',
|
|
'operation',
|
|
'plan',
|
|
'approval',
|
|
'stale',
|
|
]);
|
|
if (
|
|
result.schemaVersion !== 1 ||
|
|
result.operation !== command.operation ||
|
|
typeof result.stale !== 'boolean' ||
|
|
(result.plan === null && result.approval === null)
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
if (result.plan !== null) {
|
|
validateSecretBindingTransitionPlanSummary(result.plan, command);
|
|
}
|
|
if (result.approval !== null) {
|
|
validateScalarSummary(result.approval, APPROVAL_KEYS);
|
|
if (
|
|
(result.approval as JsonObject).id !== command.request.approvalRequestId
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
}
|
|
if (result.plan !== null && result.approval !== null) {
|
|
const plan = result.plan as JsonObject;
|
|
const approval = result.approval as JsonObject;
|
|
if (
|
|
approval.projectId !== plan.projectId ||
|
|
approval.actionDigest !== plan.approvalPlanDigest ||
|
|
approval.previewDigest !== plan.transitionDigest
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
}
|
|
return Object.freeze(
|
|
result as unknown as ClusterPluginPackageManagementTransportResult,
|
|
);
|
|
}
|
|
if (command.operation === 'plugin-package.secret-binding.plan') {
|
|
const result = exactResponseObject(value, [
|
|
'schemaVersion',
|
|
'operation',
|
|
'status',
|
|
'plan',
|
|
]);
|
|
if (
|
|
result.schemaVersion !== 1 ||
|
|
result.operation !== command.operation ||
|
|
!['created', 'existing'].includes(String(result.status))
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
validateSecretBindingPlanSummary(result.plan, command);
|
|
return Object.freeze(
|
|
result as unknown as ClusterPluginPackageManagementTransportResult,
|
|
);
|
|
}
|
|
if (command.operation === 'plugin-package.secret-binding.propose') {
|
|
const result = exactResponseObject(value, [
|
|
'schemaVersion',
|
|
'operation',
|
|
'approvalStatus',
|
|
'plan',
|
|
'approval',
|
|
]);
|
|
if (
|
|
result.schemaVersion !== 1 ||
|
|
result.operation !== command.operation ||
|
|
!['created', 'existing'].includes(String(result.approvalStatus))
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
validateSecretBindingPlanSummary(result.plan, command);
|
|
validateScalarSummary(result.approval, APPROVAL_KEYS);
|
|
const plan = result.plan as JsonObject;
|
|
const approval = result.approval as JsonObject;
|
|
if (
|
|
approval.id !== command.request.approvalRequestId ||
|
|
approval.projectId !== plan.projectId ||
|
|
approval.actionDigest !== plan.approvalPlanDigest ||
|
|
approval.previewDigest !== plan.planDigest
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
return Object.freeze(
|
|
result as unknown as ClusterPluginPackageManagementTransportResult,
|
|
);
|
|
}
|
|
if (command.operation === 'plugin-package.secret-binding.inspect') {
|
|
const result = exactResponseObject(value, [
|
|
'schemaVersion',
|
|
'operation',
|
|
'plan',
|
|
'approval',
|
|
'stale',
|
|
]);
|
|
if (
|
|
result.schemaVersion !== 1 ||
|
|
result.operation !== command.operation ||
|
|
typeof result.stale !== 'boolean' ||
|
|
(result.plan === null && result.approval === null)
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
if (result.plan !== null) {
|
|
validateSecretBindingPlanSummary(result.plan, command);
|
|
}
|
|
if (result.approval !== null) {
|
|
validateScalarSummary(result.approval, APPROVAL_KEYS);
|
|
if (
|
|
(result.approval as JsonObject).id !== command.request.approvalRequestId
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
}
|
|
if (result.plan !== null && result.approval !== null) {
|
|
const plan = result.plan as JsonObject;
|
|
const approval = result.approval as JsonObject;
|
|
if (
|
|
approval.id !== command.request.approvalRequestId ||
|
|
approval.projectId !== plan.projectId ||
|
|
approval.actionDigest !== plan.approvalPlanDigest ||
|
|
approval.previewDigest !== plan.planDigest
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
}
|
|
return Object.freeze(
|
|
result as unknown as ClusterPluginPackageManagementTransportResult,
|
|
);
|
|
}
|
|
if (command.operation === 'plugin-package.installation.inspect') {
|
|
const result = exactResponseObject(value, [
|
|
'schemaVersion',
|
|
'operation',
|
|
'installation',
|
|
]);
|
|
if (result.schemaVersion !== 1 || result.operation !== command.operation) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
if (result.installation !== null) {
|
|
validateInstallationSummary(
|
|
result.installation,
|
|
command.request.projectId,
|
|
command.request.packageName,
|
|
);
|
|
}
|
|
return Object.freeze(
|
|
result as unknown as ClusterPluginPackageManagementTransportResult,
|
|
);
|
|
}
|
|
if (command.operation === 'plugin-package.installation.list') {
|
|
const result = exactResponseObject(value, [
|
|
'schemaVersion',
|
|
'operation',
|
|
'installations',
|
|
'truncated',
|
|
'next',
|
|
]);
|
|
if (
|
|
result.schemaVersion !== 1 ||
|
|
result.operation !== command.operation ||
|
|
!Array.isArray(result.installations) ||
|
|
result.installations.length > command.request.limit ||
|
|
typeof result.truncated !== 'boolean'
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
let previousPackageName = command.request.after?.packageName ?? null;
|
|
for (const installation of result.installations) {
|
|
const summary = validateInstallationSummary(
|
|
installation,
|
|
command.request.projectId,
|
|
);
|
|
if (
|
|
previousPackageName !== null &&
|
|
String(summary.packageName) <= previousPackageName
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
previousPackageName = String(summary.packageName);
|
|
}
|
|
if (result.next !== null) {
|
|
const next = exactResponseObject(result.next, ['packageName']);
|
|
if (
|
|
typeof next.packageName !== 'string' ||
|
|
!PACKAGE_NAME_PATTERN.test(next.packageName) ||
|
|
result.truncated !== true ||
|
|
next.packageName !== previousPackageName
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
} else if (result.truncated === true) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
return Object.freeze(
|
|
result as unknown as ClusterPluginPackageManagementTransportResult,
|
|
);
|
|
}
|
|
if (command.operation === 'plugin-package.lifecycle.propose') {
|
|
const result = exactResponseObject(value, [
|
|
'schemaVersion',
|
|
'operation',
|
|
'approvalStatus',
|
|
'plan',
|
|
'approval',
|
|
]);
|
|
if (
|
|
result.schemaVersion !== 1 ||
|
|
result.operation !== command.operation ||
|
|
!['created', 'existing'].includes(String(result.approvalStatus))
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
validateLifecyclePlanSummary(result.plan);
|
|
validateScalarSummary(result.approval, APPROVAL_KEYS);
|
|
return Object.freeze(
|
|
result as unknown as ClusterPluginPackageManagementTransportResult,
|
|
);
|
|
}
|
|
if (command.operation === 'plugin-package.lifecycle.inspect') {
|
|
const result = exactResponseObject(value, [
|
|
'schemaVersion',
|
|
'operation',
|
|
'plan',
|
|
'approval',
|
|
'stale',
|
|
]);
|
|
if (
|
|
result.schemaVersion !== 1 ||
|
|
result.operation !== command.operation ||
|
|
typeof result.stale !== 'boolean'
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
if (result.plan !== null) validateLifecyclePlanSummary(result.plan);
|
|
if (result.approval !== null) {
|
|
validateScalarSummary(result.approval, APPROVAL_KEYS);
|
|
}
|
|
return Object.freeze(
|
|
result as unknown as ClusterPluginPackageManagementTransportResult,
|
|
);
|
|
}
|
|
const kind = command.operation.endsWith('.propose')
|
|
? 'propose'
|
|
: command.operation.endsWith('.decide')
|
|
? 'decide'
|
|
: 'inspect';
|
|
const result = exactResponseObject(
|
|
value,
|
|
kind === 'propose'
|
|
? [
|
|
'schemaVersion',
|
|
'operation',
|
|
'proposalStatus',
|
|
'approvalStatus',
|
|
'proposal',
|
|
'approval',
|
|
]
|
|
: kind === 'decide'
|
|
? ['schemaVersion', 'operation', 'status', 'approval']
|
|
: ['schemaVersion', 'operation', 'proposal', 'approval'],
|
|
);
|
|
if (result.schemaVersion !== 1 || result.operation !== command.operation) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
if (kind === 'propose') {
|
|
if (
|
|
!['created', 'existing'].includes(String(result.proposalStatus)) ||
|
|
!['created', 'existing'].includes(String(result.approvalStatus))
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
} else if (
|
|
kind === 'decide' &&
|
|
!['decided', 'existing'].includes(String(result.status))
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
if (result.approval !== null) {
|
|
validateScalarSummary(result.approval, APPROVAL_KEYS);
|
|
if (
|
|
(command.operation === 'plugin-package.secret-binding.decide' ||
|
|
command.operation ===
|
|
'plugin-package.secret-binding.transition.decide') &&
|
|
(result.approval as JsonObject).id !== command.request.approvalRequestId
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
} else if (kind !== 'inspect') {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
if (result.proposal !== null && result.proposal !== undefined) {
|
|
validateScalarSummary(
|
|
result.proposal,
|
|
command.operation.startsWith('plugin-package.publisher-trust-transition.')
|
|
? TRANSITION_PROPOSAL_KEYS
|
|
: command.operation.startsWith('plugin-package.publisher-revocation.')
|
|
? REVOCATION_PROPOSAL_KEYS
|
|
: INSTALL_PROPOSAL_KEYS,
|
|
);
|
|
} else if (kind === 'propose') {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
return Object.freeze(
|
|
result as unknown as ClusterPluginPackageManagementTransportResult,
|
|
);
|
|
}
|
|
|
|
function retryAfterSeconds(
|
|
value: string | string[] | undefined,
|
|
): number | null {
|
|
if (typeof value !== 'string' || !/^[1-9][0-9]{0,3}$/.test(value)) {
|
|
return null;
|
|
}
|
|
const seconds = Number(value);
|
|
return seconds <= 3_600 ? seconds : null;
|
|
}
|
|
|
|
function rawHeaderCount(rawHeaders: readonly string[], name: string): number {
|
|
let count = 0;
|
|
for (let index = 0; index < rawHeaders.length; index += 2) {
|
|
if (rawHeaders[index]?.toLowerCase() === name) count += 1;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
export async function executeClusterAuthenticatedManagementClient<
|
|
Command,
|
|
Result,
|
|
>(
|
|
execution:
|
|
| ClusterPluginPackageManagementClientPaths
|
|
| ClusterAuthenticatedManagementCommandExecution<Command>,
|
|
protocol: ClusterAuthenticatedManagementClientProtocol<Command, Result>,
|
|
connectionOptions?: ClusterPluginPackageManagementClientConnectionOptions,
|
|
): Promise<Readonly<ClusterAuthenticatedManagementClientResult<Result>>> {
|
|
const inlineCommand =
|
|
execution !== null &&
|
|
typeof execution === 'object' &&
|
|
!Array.isArray(execution) &&
|
|
Object.hasOwn(execution, 'command');
|
|
exactObject(
|
|
execution,
|
|
inlineCommand
|
|
? ['configFile', 'command', 'assertionFile']
|
|
: ['configFile', 'commandFile', 'assertionFile'],
|
|
);
|
|
if (
|
|
!protocol ||
|
|
typeof protocol !== 'object' ||
|
|
Array.isArray(protocol) ||
|
|
Object.keys(protocol).length !== 4 ||
|
|
Object.keys(protocol).some(
|
|
(key) =>
|
|
![
|
|
'managementPath',
|
|
'clientCertificate',
|
|
'normalizeCommand',
|
|
'validateResult',
|
|
].includes(key),
|
|
) ||
|
|
!isReviewedClusterAuthenticatedManagementClientProtocol(
|
|
protocol.managementPath,
|
|
protocol.clientCertificate,
|
|
) ||
|
|
typeof protocol.normalizeCommand !== 'function' ||
|
|
typeof protocol.validateResult !== 'function' ||
|
|
(connectionOptions !== undefined &&
|
|
(!connectionOptions ||
|
|
typeof connectionOptions !== 'object' ||
|
|
Array.isArray(connectionOptions) ||
|
|
Object.keys(connectionOptions).length !== 1 ||
|
|
typeof connectionOptions.connect !== 'function'))
|
|
) {
|
|
throw configurationFailure();
|
|
}
|
|
let commandBytes: Buffer | undefined;
|
|
let assertionBytes: Buffer | undefined;
|
|
let prepared:
|
|
| PreparedClusterAuthenticatedManagementClientConfiguration
|
|
| undefined;
|
|
try {
|
|
prepared = prepareClusterAuthenticatedManagementClientConfiguration(
|
|
execution.configFile,
|
|
protocol.managementPath,
|
|
protocol.clientCertificate,
|
|
);
|
|
if (!inlineCommand) {
|
|
commandBytes = readCanonicalFile(
|
|
(execution as ClusterPluginPackageManagementClientPaths).commandFile,
|
|
MAX_COMMAND_BYTES,
|
|
'private',
|
|
);
|
|
}
|
|
assertionBytes = readCanonicalFile(
|
|
execution.assertionFile,
|
|
MAX_ASSERTION_BYTES,
|
|
'private',
|
|
);
|
|
const {
|
|
endpoint,
|
|
servername,
|
|
port,
|
|
requestTimeoutMs,
|
|
caBytes,
|
|
clientCertificateBytes,
|
|
clientPrivateKeyBytes,
|
|
} = prepared;
|
|
const command = protocol.normalizeCommand(
|
|
inlineCommand
|
|
? (execution as ClusterAuthenticatedManagementCommandExecution<Command>)
|
|
.command
|
|
: parseJson(commandBytes!),
|
|
);
|
|
const assertion = assertionBytes.toString('ascii');
|
|
if (
|
|
assertionBytes.some((byte) => byte > 0x7f) ||
|
|
!ASSERTION_PATTERN.test(assertion)
|
|
) {
|
|
throw configurationFailure();
|
|
}
|
|
const body = Buffer.from(JSON.stringify(command));
|
|
if (body.length < 2 || body.length > MAX_COMMAND_BYTES) {
|
|
body.fill(0);
|
|
throw configurationFailure();
|
|
}
|
|
|
|
let rawConnection:
|
|
| ClusterPluginPackageManagementClientRawConnection
|
|
| undefined;
|
|
let connectionAgent: HttpsAgent | undefined;
|
|
try {
|
|
if (connectionOptions) {
|
|
rawConnection = await connectionOptions.connect(
|
|
Object.freeze({ hostname: endpoint.hostname, port }),
|
|
);
|
|
if (
|
|
!rawConnection ||
|
|
typeof rawConnection !== 'object' ||
|
|
!(rawConnection.stream instanceof Duplex) ||
|
|
typeof rawConnection.close !== 'function'
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
}
|
|
const establishedConnection = rawConnection;
|
|
if (establishedConnection) {
|
|
connectionAgent = new HttpsAgent({
|
|
keepAlive: false,
|
|
maxSockets: 1,
|
|
maxFreeSockets: 0,
|
|
});
|
|
connectionAgent.createConnection = (_options, callback) => {
|
|
const socket = tlsConnect({
|
|
socket: establishedConnection.stream,
|
|
ca: caBytes,
|
|
...(clientCertificateBytes === undefined
|
|
? {}
|
|
: {
|
|
cert: clientCertificateBytes,
|
|
key: clientPrivateKeyBytes,
|
|
}),
|
|
servername,
|
|
minVersion: 'TLSv1.3',
|
|
maxVersion: 'TLSv1.3',
|
|
rejectUnauthorized: true,
|
|
});
|
|
if (callback) {
|
|
let reported = false;
|
|
socket.once('secureConnect', () => {
|
|
if (reported) return;
|
|
reported = true;
|
|
callback(null, socket);
|
|
});
|
|
socket.once('error', (error) => {
|
|
if (reported) return;
|
|
reported = true;
|
|
callback(error, socket);
|
|
});
|
|
}
|
|
return socket;
|
|
};
|
|
}
|
|
return await new Promise((resolve, reject) => {
|
|
let settled = false;
|
|
const finish = (
|
|
error: unknown,
|
|
result?: Readonly<ClusterAuthenticatedManagementClientResult<Result>>,
|
|
) => {
|
|
if (settled) return;
|
|
settled = true;
|
|
body.fill(0);
|
|
if (error) reject(error);
|
|
else resolve(result!);
|
|
};
|
|
const request = httpsRequest(
|
|
{
|
|
protocol: 'https:',
|
|
hostname: endpoint.hostname,
|
|
port,
|
|
path: protocol.managementPath,
|
|
method: 'POST',
|
|
servername,
|
|
ca: caBytes,
|
|
...(clientCertificateBytes === undefined
|
|
? {}
|
|
: {
|
|
cert: clientCertificateBytes,
|
|
key: clientPrivateKeyBytes,
|
|
}),
|
|
minVersion: 'TLSv1.3',
|
|
maxVersion: 'TLSv1.3',
|
|
rejectUnauthorized: true,
|
|
agent: connectionAgent ?? false,
|
|
headers: {
|
|
accept: 'application/json',
|
|
'accept-encoding': 'identity',
|
|
authorization: `Bearer ${assertion}`,
|
|
connection: 'close',
|
|
'content-type': 'application/json',
|
|
'content-length': String(body.length),
|
|
},
|
|
},
|
|
(response) => {
|
|
const chunks: Buffer[] = [];
|
|
let length = 0;
|
|
const clearChunks = () => {
|
|
for (const chunk of chunks) chunk.fill(0);
|
|
};
|
|
response.once('aborted', () => {
|
|
clearChunks();
|
|
finish(new ClusterPluginPackageManagementClientRequestError());
|
|
});
|
|
response.once('error', (error) => {
|
|
clearChunks();
|
|
finish(
|
|
new ClusterPluginPackageManagementClientRequestError(error),
|
|
);
|
|
});
|
|
response.on('data', (chunk: Buffer | string) => {
|
|
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
length += bytes.length;
|
|
if (length > MAX_RESPONSE_BYTES) {
|
|
const error =
|
|
new ClusterPluginPackageManagementClientRequestError();
|
|
bytes.fill(0);
|
|
clearChunks();
|
|
response.destroy();
|
|
request.destroy(error);
|
|
finish(error);
|
|
return;
|
|
}
|
|
chunks.push(bytes);
|
|
});
|
|
response.once('end', () => {
|
|
try {
|
|
if (
|
|
rawHeaderCount(response.rawHeaders, 'content-type') !== 1 ||
|
|
response.headers['content-type'] !==
|
|
'application/json; charset=utf-8' ||
|
|
response.headers['content-encoding'] !== undefined ||
|
|
rawHeaderCount(response.rawHeaders, 'content-length') > 1 ||
|
|
(response.headers['content-length'] !== undefined &&
|
|
(!/^(?:0|[1-9][0-9]*)$/.test(
|
|
response.headers['content-length'],
|
|
) ||
|
|
Number(response.headers['content-length']) !== length))
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
const bytes = Buffer.concat(chunks, length);
|
|
let envelope: unknown;
|
|
try {
|
|
envelope = JSON.parse(
|
|
new TextDecoder('utf-8', { fatal: true }).decode(bytes),
|
|
);
|
|
} finally {
|
|
bytes.fill(0);
|
|
clearChunks();
|
|
}
|
|
const statusCode = response.statusCode ?? 0;
|
|
if (statusCode === 200) {
|
|
const record = exactResponseObject(envelope, [
|
|
'schemaVersion',
|
|
'requestId',
|
|
'result',
|
|
]);
|
|
const requestId = boundedString(record.requestId, 128);
|
|
if (record.schemaVersion !== 1) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
finish(
|
|
undefined,
|
|
Object.freeze({
|
|
schemaVersion: 1,
|
|
requestId,
|
|
result: protocol.validateResult(record.result, command),
|
|
}),
|
|
);
|
|
return;
|
|
}
|
|
const record = exactResponseObject(envelope, [
|
|
'schemaVersion',
|
|
'requestId',
|
|
'error',
|
|
]);
|
|
const error = exactResponseObject(record.error, ['code']);
|
|
if (
|
|
record.schemaVersion !== 1 ||
|
|
statusCode < 400 ||
|
|
statusCode > 599
|
|
) {
|
|
throw new ClusterPluginPackageManagementClientRequestError();
|
|
}
|
|
finish(
|
|
new ClusterPluginPackageManagementClientRemoteError(
|
|
statusCode,
|
|
boundedString(error.code, 128),
|
|
boundedString(record.requestId, 128),
|
|
retryAfterSeconds(response.headers['retry-after']),
|
|
),
|
|
);
|
|
} catch (error) {
|
|
finish(
|
|
error instanceof
|
|
ClusterPluginPackageManagementClientRemoteError ||
|
|
error instanceof
|
|
ClusterPluginPackageManagementClientRequestError
|
|
? error
|
|
: new ClusterPluginPackageManagementClientRequestError(
|
|
error,
|
|
),
|
|
);
|
|
}
|
|
});
|
|
},
|
|
);
|
|
request.setTimeout(requestTimeoutMs, () => {
|
|
request.destroy(
|
|
new ClusterPluginPackageManagementClientRequestError(),
|
|
);
|
|
});
|
|
request.once('error', (error) => {
|
|
finish(
|
|
error instanceof ClusterPluginPackageManagementClientRequestError
|
|
? error
|
|
: new ClusterPluginPackageManagementClientRequestError(error),
|
|
);
|
|
});
|
|
request.end(body);
|
|
});
|
|
} catch (error) {
|
|
if (
|
|
error instanceof ClusterPluginPackageManagementClientRemoteError ||
|
|
error instanceof ClusterPluginPackageManagementClientRequestError
|
|
) {
|
|
throw error;
|
|
}
|
|
throw new ClusterPluginPackageManagementClientRequestError(error);
|
|
} finally {
|
|
connectionAgent?.destroy();
|
|
try {
|
|
await rawConnection?.close();
|
|
} catch {
|
|
// Resource cleanup cannot replace the already observed command outcome.
|
|
}
|
|
}
|
|
} finally {
|
|
commandBytes?.fill(0);
|
|
assertionBytes?.fill(0);
|
|
prepared?.dispose();
|
|
}
|
|
}
|
|
|
|
const PLUGIN_PACKAGE_MANAGEMENT_CLIENT_PROTOCOL = Object.freeze({
|
|
managementPath: MANAGEMENT_PATH,
|
|
clientCertificate: 'forbidden' as const,
|
|
normalizeCommand: normalizeClusterPluginPackageManagementCommand,
|
|
validateResult,
|
|
});
|
|
|
|
export async function executeClusterPluginPackageManagementClient(
|
|
paths: ClusterPluginPackageManagementClientPaths,
|
|
connectionOptions?: ClusterPluginPackageManagementClientConnectionOptions,
|
|
): Promise<Readonly<ClusterPluginPackageManagementClientResult>> {
|
|
return executeClusterAuthenticatedManagementClient(
|
|
paths,
|
|
PLUGIN_PACKAGE_MANAGEMENT_CLIENT_PROTOCOL,
|
|
connectionOptions,
|
|
);
|
|
}
|
|
|
|
export async function executeClusterPluginPackageManagementCommand(
|
|
execution: ClusterAuthenticatedManagementCommandExecution<ClusterPluginPackageManagementCommand>,
|
|
connectionOptions?: ClusterPluginPackageManagementClientConnectionOptions,
|
|
): Promise<Readonly<ClusterPluginPackageManagementClientResult>> {
|
|
return executeClusterAuthenticatedManagementClient(
|
|
execution,
|
|
PLUGIN_PACKAGE_MANAGEMENT_CLIENT_PROTOCOL,
|
|
connectionOptions,
|
|
);
|
|
}
|