feat(ql3): add read-only cluster context readiness

This commit is contained in:
whyour
2026-08-13 02:14:17 +08:00
parent a83b4c5e8d
commit a6ad636251
18 changed files with 1529 additions and 391 deletions
@@ -1,20 +1,28 @@
/** Shared one-shot authenticated client boundary for cluster management planes. */
import {
closeSync,
constants,
fstatSync,
lstatSync,
openSync,
readSync,
realpathSync,
} from 'node:fs';
import { Agent as HttpsAgent, request as httpsRequest } from 'node:https';
import { isIP } from 'node:net';
import { isAbsolute } from 'node:path';
import { Duplex } from 'node:stream';
import { connect as tlsConnect } from 'node:tls';
import { TextDecoder } from 'node:util';
import { createPrivateKey, X509Certificate } from 'node:crypto';
import {
ClusterPluginPackageManagementClientConfigurationError,
isReviewedClusterAuthenticatedManagementClientProtocol,
prepareClusterAuthenticatedManagementClientConfiguration,
readCanonicalFile,
validateClusterAuthenticatedManagementClientConfiguration,
type ClusterAuthenticatedManagementClientConfigurationSummary,
type ClusterAuthenticatedManagementClientKind,
type PreparedClusterAuthenticatedManagementClientConfiguration,
} from './managementClientConfiguration';
export {
ClusterPluginPackageManagementClientConfigurationError,
readCanonicalFile,
validateClusterAuthenticatedManagementClientConfiguration,
};
export type {
ClusterAuthenticatedManagementClientConfigurationSummary,
ClusterAuthenticatedManagementClientKind,
};
import {
normalizeClusterPluginPackageManagementCommand,
@@ -23,59 +31,11 @@ import {
} from '../plugin-package/management/pluginPackageManagementTransport';
const MANAGEMENT_PATH = '/api/v3/plugin-packages/management';
export type ClusterAuthenticatedManagementClientKind =
| 'package'
| 'worker-credential'
| 'automation'
| 'approval'
| 'model-credential'
| 'run';
const MANAGEMENT_CLIENT_POLICIES: Readonly<
Record<
ClusterAuthenticatedManagementClientKind,
Readonly<{
managementPath: string;
clientCertificate: 'forbidden' | 'required';
}>
>
> = Object.freeze({
package: Object.freeze({
managementPath: MANAGEMENT_PATH,
clientCertificate: 'forbidden',
}),
'worker-credential': Object.freeze({
managementPath: '/api/v3/worker-credentials/management',
clientCertificate: 'required',
}),
automation: Object.freeze({
managementPath: '/api/v3/automations/management',
clientCertificate: 'required',
}),
approval: Object.freeze({
managementPath: '/api/v3/approvals/management',
clientCertificate: 'required',
}),
'model-credential': Object.freeze({
managementPath: '/api/v3/provider-credentials/management',
clientCertificate: 'required',
}),
run: Object.freeze({
managementPath: '/api/v3/runs/management',
clientCertificate: 'required',
}),
});
const MAX_CONFIG_BYTES = 16 * 1024;
const MAX_ASSERTION_BYTES = 16 * 1024;
const MAX_COMMAND_BYTES = 256 * 1024;
const MAX_CA_BYTES = 256 * 1024;
const MAX_CLIENT_CERTIFICATE_BYTES = 256 * 1024;
const MAX_CLIENT_PRIVATE_KEY_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 DNS_NAME_PATTERN =
/^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-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}$/;
@@ -123,15 +83,6 @@ export interface ClusterPluginPackageManagementClientConnectionOptions {
): Promise<ClusterPluginPackageManagementClientRawConnection>;
}
export class ClusterPluginPackageManagementClientConfigurationError extends TypeError {
readonly code = 'QL3_PLUGIN_PACKAGE_MANAGEMENT_CLIENT_CONFIG_INVALID';
constructor() {
super('Plugin Package management client configuration is invalid');
this.name = 'ClusterPluginPackageManagementClientConfigurationError';
}
}
export class ClusterPluginPackageManagementClientRequestError extends Error {
readonly code = 'QL3_PLUGIN_PACKAGE_MANAGEMENT_CLIENT_REQUEST_FAILED';
@@ -176,114 +127,6 @@ function exactObject(
}
}
function currentUid(): number {
if (typeof process.getuid !== 'function') throw configurationFailure();
const uid = process.getuid();
if (!Number.isSafeInteger(uid) || uid < 0) throw configurationFailure();
return uid;
}
export function readCanonicalFile(
filePath: string,
maximumBytes: number,
mode: 'private' | 'public-integrity',
): Buffer {
if (
typeof filePath !== 'string' ||
!isAbsolute(filePath) ||
filePath.length > 4_096 ||
CONTROL_PATTERN.test(filePath)
) {
throw configurationFailure();
}
let before;
try {
before = lstatSync(filePath);
if (
!before.isFile() ||
before.isSymbolicLink() ||
before.size < 1 ||
before.size > maximumBytes ||
realpathSync(filePath) !== filePath
) {
throw configurationFailure();
}
} catch (error) {
if (
error instanceof ClusterPluginPackageManagementClientConfigurationError
) {
throw error;
}
throw configurationFailure();
}
const uid = currentUid();
const permissions = before.mode & 0o777;
if (
(mode === 'private' && (before.uid !== uid || permissions !== 0o600)) ||
(mode === 'public-integrity' && before.uid !== uid && before.uid !== 0) ||
(mode === 'public-integrity' && (permissions & 0o022) !== 0)
) {
throw configurationFailure();
}
let descriptor = -1;
let bytes: Buffer | undefined;
try {
descriptor = openSync(
filePath,
constants.O_RDONLY |
((constants as unknown as Readonly<Record<string, number>>).O_CLOEXEC ??
0) |
(constants.O_NOFOLLOW ?? 0),
);
const opened = fstatSync(descriptor);
if (
!opened.isFile() ||
opened.dev !== before.dev ||
opened.ino !== before.ino ||
opened.uid !== before.uid ||
opened.mode !== before.mode ||
opened.size !== before.size
) {
throw configurationFailure();
}
bytes = Buffer.alloc(opened.size);
let offset = 0;
while (offset < bytes.length) {
const count = readSync(
descriptor,
bytes,
offset,
bytes.length - offset,
offset,
);
if (count < 1) throw configurationFailure();
offset += count;
}
const after = fstatSync(descriptor);
if (
after.dev !== opened.dev ||
after.ino !== opened.ino ||
after.uid !== opened.uid ||
after.mode !== opened.mode ||
after.size !== opened.size
) {
throw configurationFailure();
}
return bytes;
} catch (error) {
bytes?.fill(0);
if (
error instanceof ClusterPluginPackageManagementClientConfigurationError
) {
throw error;
}
throw configurationFailure();
} finally {
if (descriptor >= 0) closeSync(descriptor);
}
}
function decodeUtf8(bytes: Buffer): string {
try {
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
@@ -812,201 +655,6 @@ function rawHeaderCount(rawHeaders: readonly string[], name: string): number {
return count;
}
export interface ClusterAuthenticatedManagementClientConfigurationSummary {
readonly schemaVersion: 1;
readonly managementPath: string;
readonly transport: 'https';
readonly clientCertificate: 'forbidden' | 'required';
}
interface PreparedClusterAuthenticatedManagementClientConfiguration {
readonly endpoint: URL;
readonly servername: string;
readonly port: number;
readonly requestTimeoutMs: number;
readonly caBytes: Buffer;
readonly clientCertificateBytes?: Buffer;
readonly clientPrivateKeyBytes?: Buffer;
dispose(): void;
}
function prepareClusterAuthenticatedManagementClientConfiguration(
configFile: string,
managementPath: string,
clientCertificate: 'forbidden' | 'required',
): PreparedClusterAuthenticatedManagementClientConfiguration {
if (
!Object.values(MANAGEMENT_CLIENT_POLICIES).some(
(policy) =>
policy.managementPath === managementPath &&
policy.clientCertificate === clientCertificate,
)
) {
throw configurationFailure();
}
let configBytes: Buffer | undefined;
let caBytes: Buffer | undefined;
let clientCertificateBytes: Buffer | undefined;
let clientPrivateKeyBytes: Buffer | undefined;
try {
configBytes = readCanonicalFile(configFile, MAX_CONFIG_BYTES, 'private');
const config = parseJson(configBytes);
exactObject(
config,
clientCertificate === 'required'
? [
'schemaVersion',
'endpoint',
'servername',
'caFile',
'clientCertificateFile',
'clientPrivateKeyFile',
'requestTimeoutMs',
]
: [
'schemaVersion',
'endpoint',
'servername',
'caFile',
'requestTimeoutMs',
],
);
if (
config.schemaVersion !== 1 ||
typeof config.endpoint !== 'string' ||
typeof config.servername !== 'string' ||
!DNS_NAME_PATTERN.test(config.servername) ||
isIP(config.servername) !== 0 ||
typeof config.caFile !== 'string' ||
(clientCertificate === 'required' &&
(typeof config.clientCertificateFile !== 'string' ||
typeof config.clientPrivateKeyFile !== 'string')) ||
!Number.isSafeInteger(config.requestTimeoutMs) ||
(config.requestTimeoutMs as number) < 1_000 ||
(config.requestTimeoutMs as number) > 30_000
) {
throw configurationFailure();
}
const servername = config.servername;
const requestTimeoutMs = config.requestTimeoutMs as number;
let endpoint: URL;
try {
endpoint = new URL(config.endpoint);
} catch {
throw configurationFailure();
}
if (
endpoint.protocol !== 'https:' ||
endpoint.username !== '' ||
endpoint.password !== '' ||
endpoint.search !== '' ||
endpoint.hash !== '' ||
endpoint.pathname !== managementPath ||
endpoint.hostname !== servername ||
isIP(endpoint.hostname) !== 0
) {
throw configurationFailure();
}
const port = endpoint.port === '' ? 443 : Number(endpoint.port);
if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) {
throw configurationFailure();
}
caBytes = readCanonicalFile(
config.caFile as string,
MAX_CA_BYTES,
'public-integrity',
);
try {
new X509Certificate(caBytes);
} catch {
throw configurationFailure();
}
if (clientCertificate === 'required') {
clientCertificateBytes = readCanonicalFile(
config.clientCertificateFile as string,
MAX_CLIENT_CERTIFICATE_BYTES,
'public-integrity',
);
clientPrivateKeyBytes = readCanonicalFile(
config.clientPrivateKeyFile as string,
MAX_CLIENT_PRIVATE_KEY_BYTES,
'private',
);
try {
const certificate = new X509Certificate(clientCertificateBytes);
const privateKey = createPrivateKey(clientPrivateKeyBytes);
if (!certificate.checkPrivateKey(privateKey)) {
throw configurationFailure();
}
} catch (error) {
if (
error instanceof
ClusterPluginPackageManagementClientConfigurationError
) {
throw error;
}
throw configurationFailure();
}
}
let disposed = false;
return Object.freeze({
endpoint,
servername,
port,
requestTimeoutMs,
caBytes,
...(clientCertificateBytes === undefined
? {}
: {
clientCertificateBytes,
clientPrivateKeyBytes: clientPrivateKeyBytes!,
}),
dispose() {
if (disposed) return;
disposed = true;
caBytes?.fill(0);
clientCertificateBytes?.fill(0);
clientPrivateKeyBytes?.fill(0);
},
});
} catch (error) {
caBytes?.fill(0);
clientCertificateBytes?.fill(0);
clientPrivateKeyBytes?.fill(0);
if (
error instanceof ClusterPluginPackageManagementClientConfigurationError
) {
throw error;
}
throw configurationFailure();
} finally {
configBytes?.fill(0);
}
}
export function validateClusterAuthenticatedManagementClientConfiguration(
configFile: string,
kind: ClusterAuthenticatedManagementClientKind,
): Readonly<ClusterAuthenticatedManagementClientConfigurationSummary> {
const policy = MANAGEMENT_CLIENT_POLICIES[kind];
if (policy === undefined) throw configurationFailure();
const prepared = prepareClusterAuthenticatedManagementClientConfiguration(
configFile,
policy.managementPath,
policy.clientCertificate,
);
try {
return Object.freeze({
schemaVersion: 1,
managementPath: policy.managementPath,
transport: 'https',
clientCertificate: policy.clientCertificate,
});
} finally {
prepared.dispose();
}
}
export async function executeClusterAuthenticatedManagementClient<
Command,
Result,
@@ -1030,10 +678,9 @@ export async function executeClusterAuthenticatedManagementClient<
'validateResult',
].includes(key),
) ||
!Object.values(MANAGEMENT_CLIENT_POLICIES).some(
(policy) =>
policy.managementPath === protocol.managementPath &&
policy.clientCertificate === protocol.clientCertificate,
!isReviewedClusterAuthenticatedManagementClientProtocol(
protocol.managementPath,
protocol.clientCertificate,
) ||
typeof protocol.normalizeCommand !== 'function' ||
typeof protocol.validateResult !== 'function' ||