feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
@@ -0,0 +1,341 @@
// Transport owns authenticated, Policy-fenced and synchronously audited admission.
import { randomUUID } from 'node:crypto';
import {
normalizeSecurityPolicyDecision,
normalizeSecurityPrincipal,
type SecurityPolicyDecision,
type SecurityPrincipal,
} from '@qinglong/runtime-core/security';
import {
normalizeSecurityAuditRecord,
type SecurityAuditOutcome,
type SecurityAuditRecord,
type SecurityAuditSink,
} from '@qinglong/runtime-core/security-audit';
import {
ProjectPolicyEngine,
normalizeProjectPermission,
type ProjectPolicyRepository,
} from '@qinglong/runtime-core/project-policy';
import type {
ClusterControlAdmissionMetadata,
ClusterControlAdmissionPipeline,
} from './httpSurface';
import {
ClusterControlRouteResolutionError,
isClusterControlRouteRegistry,
type ClusterControlAuthorizedOperationRequest,
type ClusterControlRoute,
type ClusterControlRouteRegistry,
} from './routeRegistry';
export type {
ClusterControlAuthorizedOperationRequest,
ClusterControlRoute,
ClusterControlRouteRegistry,
} from './routeRegistry';
export interface ClusterControlRequestAuthenticator {
authenticate(
request: ClusterControlAdmissionMetadata,
): SecurityPrincipal | null | Promise<SecurityPrincipal | null>;
}
export interface ClusterControlPolicyRequest {
readonly principal: Readonly<SecurityPrincipal>;
readonly operationId: string;
readonly permission: string;
readonly projectId: string | null;
readonly signal: AbortSignal;
}
export interface ClusterControlPolicyAuthorizer {
authorize(
request: ClusterControlPolicyRequest,
): SecurityPolicyDecision | Promise<SecurityPolicyDecision>;
}
export type ClusterControlSecurityAuditOutcome = SecurityAuditOutcome;
export type ClusterControlSecurityAuditRecord = SecurityAuditRecord;
export type ClusterControlSecurityAuditSink = SecurityAuditSink;
export interface ClusterControlAdmissionPipelineOptions {
readonly routes: ClusterControlRouteRegistry;
readonly authenticator: ClusterControlRequestAuthenticator;
readonly policy: ClusterControlPolicyAuthorizer;
readonly audit: ClusterControlSecurityAuditSink;
readonly now?: () => number;
}
export class ClusterControlAdmissionSecurityError extends Error {
constructor(
readonly statusCode: number,
readonly code: string,
message: string,
) {
super(message);
this.name = 'ClusterControlAdmissionSecurityError';
}
}
/** Adapts the shared Project Policy engine to cluster admission. */
export function createClusterControlProjectPolicyAuthorizer(
repository: ProjectPolicyRepository,
): ClusterControlPolicyAuthorizer {
const engine = new ProjectPolicyEngine(repository);
return Object.freeze({
authorize(request: ClusterControlPolicyRequest) {
if (request.projectId === null) {
return Object.freeze({
effect: 'deny' as const,
reasons: Object.freeze(['project_scope_required']),
fence: null,
});
}
return engine.authorize(
request.principal,
request.projectId,
normalizeProjectPermission(request.permission),
);
},
});
}
function securityError(
statusCode: number,
code: string,
message: string,
): ClusterControlAdmissionSecurityError {
return new ClusterControlAdmissionSecurityError(statusCode, code, message);
}
async function recordSecurityAudit(
audit: ClusterControlSecurityAuditSink,
record: Omit<SecurityAuditRecord, 'eventId' | 'occurredAtMs'>,
now: () => number,
): Promise<void> {
try {
await audit.record(
normalizeSecurityAuditRecord({
...record,
eventId: randomUUID(),
occurredAtMs: now(),
}),
);
} catch {
throw securityError(
503,
'security_audit_unavailable',
'Cluster-control security audit is unavailable',
);
}
}
/**
* Creates a fail-closed, two-phase admission pipeline. Route matching,
* authentication, policy evaluation and durable security audit all complete
* before the returned operation is allowed to receive a request body.
*/
export function createClusterControlAdmissionPipeline(
options: ClusterControlAdmissionPipelineOptions,
): ClusterControlAdmissionPipeline {
if (
!options ||
typeof options !== 'object' ||
!isClusterControlRouteRegistry(options.routes) ||
typeof options.authenticator?.authenticate !== 'function' ||
typeof options.policy?.authorize !== 'function' ||
typeof options.audit?.record !== 'function' ||
(options.now !== undefined && typeof options.now !== 'function')
) {
throw new TypeError(
'Cluster-control admission pipeline options are invalid',
);
}
const now = options.now ?? Date.now;
return Object.freeze({
async prepare(request: ClusterControlAdmissionMetadata) {
let route: ClusterControlRoute;
try {
const resolved = await options.routes.resolve(request);
if (!resolved) {
throw securityError(
404,
'route_not_found',
'Cluster-control route was not found',
);
}
route = resolved;
} catch (error) {
if (
error instanceof ClusterControlAdmissionSecurityError ||
error instanceof ClusterControlRouteResolutionError
) {
throw error;
}
throw securityError(
503,
'route_resolution_unavailable',
'Cluster-control route resolution is unavailable',
);
}
let candidate: SecurityPrincipal | null;
try {
candidate = await options.authenticator.authenticate(request);
} catch {
await recordSecurityAudit(
options.audit,
{
requestId: request.requestId,
operationId: route.operationId,
projectId: route.projectId,
subject: null,
authenticationId: null,
outcome: 'authentication_unavailable',
reasons: Object.freeze(['authentication_unavailable']),
fence: null,
},
now,
);
throw securityError(
503,
'authentication_unavailable',
'Cluster-control authentication is unavailable',
);
}
if (!candidate) {
await recordSecurityAudit(
options.audit,
{
requestId: request.requestId,
operationId: route.operationId,
projectId: route.projectId,
subject: null,
authenticationId: null,
outcome: 'authentication_rejected',
reasons: Object.freeze(['authentication_rejected']),
fence: null,
},
now,
);
throw securityError(
401,
'authentication_required',
'Cluster-control authentication is required',
);
}
let principal: Readonly<SecurityPrincipal>;
try {
principal = normalizeSecurityPrincipal(candidate, now());
} catch {
await recordSecurityAudit(
options.audit,
{
requestId: request.requestId,
operationId: route.operationId,
projectId: route.projectId,
subject: null,
authenticationId: null,
outcome: 'authentication_unavailable',
reasons: Object.freeze(['invalid_principal']),
fence: null,
},
now,
);
throw securityError(
503,
'authentication_unavailable',
'Cluster-control authentication is unavailable',
);
}
let decision: Readonly<SecurityPolicyDecision>;
try {
decision = normalizeSecurityPolicyDecision(
await options.policy.authorize(
Object.freeze({
principal,
operationId: route.operationId,
permission: route.permission,
projectId: route.projectId,
signal: request.signal,
}),
),
);
} catch {
await recordSecurityAudit(
options.audit,
{
requestId: request.requestId,
operationId: route.operationId,
projectId: route.projectId,
subject: principal.subject,
authenticationId: principal.authenticationId,
outcome: 'authorization_unavailable',
reasons: Object.freeze(['authorization_unavailable']),
fence: null,
},
now,
);
throw securityError(
503,
'authorization_unavailable',
'Cluster-control authorization is unavailable',
);
}
const outcome =
decision.effect === 'allow'
? 'allowed'
: decision.effect === 'require_approval'
? 'approval_required'
: 'denied';
await recordSecurityAudit(
options.audit,
{
requestId: request.requestId,
operationId: route.operationId,
projectId: route.projectId,
subject: principal.subject,
authenticationId: principal.authenticationId,
outcome,
reasons: decision.reasons,
fence: decision.fence,
},
now,
);
if (decision.effect === 'deny') {
throw securityError(
403,
'forbidden',
'Cluster-control operation is forbidden',
);
}
if (decision.effect === 'require_approval') {
throw securityError(
403,
'approval_required',
'Cluster-control operation requires approval',
);
}
return Object.freeze({
handle(body: unknown | null) {
return route.handle(
Object.freeze({
request: Object.freeze({ ...request, body }),
principal,
operationId: route.operationId,
permission: route.permission,
projectId: route.projectId,
policyFence: decision.fence,
}),
);
},
});
},
});
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,452 @@
// Transport owns bounded route compilation, resolution and query validation.
import type {
SecurityPolicyFence,
SecurityPrincipal,
} from '@qinglong/runtime-core/security';
import type {
ClusterControlAdmissionMetadata,
ClusterControlAdmissionRequest,
ClusterControlAdmissionResponse,
ClusterControlHttpMethod,
} from './httpSurface';
export const CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS = Object.freeze({
maxRoutes: 256,
maxPathBytes: 1024,
maxPathSegments: 16,
maxPathParameters: 8,
maxQueryParameters: 16,
maxQueryValuesPerParameter: 16,
maxQueryValueBytes: 1024,
});
export type ClusterControlRouteParameters = Readonly<Record<string, string>>;
export interface ClusterControlAuthorizedOperationRequest {
readonly request: ClusterControlAdmissionRequest;
readonly principal: Readonly<SecurityPrincipal>;
readonly operationId: string;
readonly permission: string;
readonly projectId: string | null;
readonly policyFence: Readonly<SecurityPolicyFence> | null;
}
export interface ClusterControlRouteDefinition {
readonly method: ClusterControlHttpMethod;
readonly path: string;
readonly operationId: string;
readonly permission: string;
readonly projectParameter: string | null;
readonly allowedQuery?: readonly string[];
readonly validateQuery?: (
query: Readonly<Record<string, readonly string[]>>,
) => void;
handle(
request: ClusterControlAuthorizedOperationRequest,
parameters: ClusterControlRouteParameters,
): ClusterControlAdmissionResponse | Promise<ClusterControlAdmissionResponse>;
}
export interface ClusterControlRoute {
readonly operationId: string;
readonly permission: string;
readonly projectId: string | null;
handle(
request: ClusterControlAuthorizedOperationRequest,
): ClusterControlAdmissionResponse | Promise<ClusterControlAdmissionResponse>;
}
export interface ClusterControlRouteResolver {
resolve(request: ClusterControlAdmissionMetadata): ClusterControlRoute | null;
}
export interface ClusterControlRouteRegistry
extends ClusterControlRouteResolver {
readonly contractVersion: 1;
readonly size: number;
}
export class ClusterControlRouteRegistryConfigurationError extends TypeError {
constructor(message: string) {
super(`Cluster-control route registry is invalid: ${message}`);
this.name = 'ClusterControlRouteRegistryConfigurationError';
}
}
export class ClusterControlRouteResolutionError extends Error {
constructor(
readonly statusCode: 400,
readonly code: 'invalid_route_path' | 'invalid_route_query',
message: string,
) {
super(message);
this.name = 'ClusterControlRouteResolutionError';
}
}
type CompiledSegment =
| { readonly kind: 'literal'; readonly value: string }
| { readonly kind: 'parameter'; readonly name: string };
interface CompiledRoute {
readonly method: ClusterControlHttpMethod;
readonly operationId: string;
readonly permission: string;
readonly projectParameter: string | null;
readonly segments: readonly CompiledSegment[];
readonly allowedQuery: ReadonlySet<string>;
readonly validateQuery?: ClusterControlRouteDefinition['validateQuery'];
readonly handle: ClusterControlRouteDefinition['handle'];
}
const HTTP_METHODS = new Set<ClusterControlHttpMethod>([
'DELETE',
'GET',
'PATCH',
'POST',
'PUT',
]);
const OPERATION_PATTERN = /^[a-z][a-z0-9_.:-]{0,127}$/;
const PERMISSION_PATTERN = /^[a-z][a-z0-9_.:*:-]{0,127}$/;
const LITERAL_SEGMENT_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
const PARAMETER_NAME_PATTERN = /^[a-z][A-Za-z0-9]{0,63}$/;
const PARAMETER_SEGMENT_PATTERN = /^\{([a-z][A-Za-z0-9]{0,63})\}$/;
const PARAMETER_VALUE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const QUERY_NAME_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/;
const DEFINITION_KEYS = new Set([
'allowedQuery',
'handle',
'method',
'operationId',
'path',
'permission',
'projectParameter',
'validateQuery',
]);
const reviewedRegistries = new WeakSet<object>();
function configurationError(
message: string,
): ClusterControlRouteRegistryConfigurationError {
return new ClusterControlRouteRegistryConfigurationError(message);
}
function exactDefinitionShape(value: ClusterControlRouteDefinition): void {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw configurationError('each route must be an object');
}
const keys = Object.keys(value);
if (
keys.some((key) => !DEFINITION_KEYS.has(key)) ||
!keys.includes('method') ||
!keys.includes('path') ||
!keys.includes('operationId') ||
!keys.includes('permission') ||
!keys.includes('projectParameter') ||
!keys.includes('handle')
) {
throw configurationError('route shape is invalid');
}
}
function compilePath(path: string): readonly CompiledSegment[] {
if (
typeof path !== 'string' ||
Buffer.byteLength(path) >
CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxPathBytes ||
!path.startsWith('/api/v3/') ||
path.endsWith('/') ||
path.includes('//') ||
path.includes('%') ||
path.includes('\\') ||
CONTROL_CHARACTER_PATTERN.test(path)
) {
throw configurationError('route path must be a canonical /api/v3 path');
}
const rawSegments = path.slice(1).split('/');
if (
rawSegments.length > CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxPathSegments
) {
throw configurationError('route path has too many segments');
}
const parameterNames = new Set<string>();
const compiled = rawSegments.map((segment): CompiledSegment => {
const parameter = PARAMETER_SEGMENT_PATTERN.exec(segment)?.[1];
if (parameter) {
if (parameterNames.has(parameter)) {
throw configurationError('route path repeats a parameter');
}
parameterNames.add(parameter);
return Object.freeze({ kind: 'parameter', name: parameter });
}
if (!LITERAL_SEGMENT_PATTERN.test(segment)) {
throw configurationError('route path contains an invalid segment');
}
return Object.freeze({ kind: 'literal', value: segment });
});
if (
parameterNames.size >
CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxPathParameters
) {
throw configurationError('route path has too many parameters');
}
return Object.freeze(compiled);
}
function compileAllowedQuery(
value: readonly string[] | undefined,
): ReadonlySet<string> {
if (value === undefined) return new Set<string>();
if (
!Array.isArray(value) ||
value.length > CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxQueryParameters
) {
throw configurationError('allowedQuery is invalid');
}
const names = new Set<string>();
for (const name of value) {
if (!QUERY_NAME_PATTERN.test(name) || names.has(name)) {
throw configurationError('allowedQuery contains an invalid name');
}
names.add(name);
}
return names;
}
function compileRoute(
definition: ClusterControlRouteDefinition,
): CompiledRoute {
exactDefinitionShape(definition);
if (!HTTP_METHODS.has(definition.method)) {
throw configurationError('route method is invalid');
}
if (!OPERATION_PATTERN.test(definition.operationId)) {
throw configurationError('route operationId is invalid');
}
if (!PERMISSION_PATTERN.test(definition.permission)) {
throw configurationError('route permission is invalid');
}
if (typeof definition.handle !== 'function') {
throw configurationError('route handler is invalid');
}
if (
definition.validateQuery !== undefined &&
typeof definition.validateQuery !== 'function'
) {
throw configurationError('route query validator is invalid');
}
const segments = compilePath(definition.path);
const parameterNames = new Set(
segments.flatMap((segment) =>
segment.kind === 'parameter' ? [segment.name] : [],
),
);
if (
definition.projectParameter !== null &&
(!PARAMETER_NAME_PATTERN.test(definition.projectParameter) ||
!parameterNames.has(definition.projectParameter))
) {
throw configurationError(
'projectParameter must name one declared path parameter',
);
}
return Object.freeze({
method: definition.method,
operationId: definition.operationId,
permission: definition.permission,
projectParameter: definition.projectParameter,
segments,
allowedQuery: compileAllowedQuery(definition.allowedQuery),
...(definition.validateQuery === undefined
? {}
: { validateQuery: definition.validateQuery }),
handle: definition.handle,
});
}
function routesOverlap(left: CompiledRoute, right: CompiledRoute): boolean {
if (
left.method !== right.method ||
left.segments.length !== right.segments.length
) {
return false;
}
return left.segments.every((segment, index) => {
const other = right.segments[index]!;
return (
segment.kind === 'parameter' ||
other.kind === 'parameter' ||
segment.value === other.value
);
});
}
function validateRequestPath(path: string): readonly string[] {
if (
typeof path !== 'string' ||
Buffer.byteLength(path) >
CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxPathBytes ||
!(path === '/api/v3' || path.startsWith('/api/v3/')) ||
path.endsWith('/') ||
path.includes('//') ||
path.includes('%') ||
path.includes('\\') ||
CONTROL_CHARACTER_PATTERN.test(path)
) {
throw new ClusterControlRouteResolutionError(
400,
'invalid_route_path',
'Cluster-control route path is invalid',
);
}
const segments = path.slice(1).split('/');
if (
segments.length > CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxPathSegments ||
segments.some((segment) => !PARAMETER_VALUE_PATTERN.test(segment))
) {
throw new ClusterControlRouteResolutionError(
400,
'invalid_route_path',
'Cluster-control route path is invalid',
);
}
return segments;
}
function validateQuery(
query: Readonly<Record<string, readonly string[]>>,
allowed: ReadonlySet<string>,
): void {
if (!query || typeof query !== 'object' || Array.isArray(query)) {
throw new ClusterControlRouteResolutionError(
400,
'invalid_route_query',
'Cluster-control route query is invalid',
);
}
const names = Object.keys(query);
for (const name of names) {
const values = query[name];
if (
!allowed.has(name) ||
!Array.isArray(values) ||
values.length === 0 ||
values.length >
CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxQueryValuesPerParameter ||
values.some(
(value) =>
typeof value !== 'string' ||
Buffer.byteLength(value) >
CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxQueryValueBytes ||
CONTROL_CHARACTER_PATTERN.test(value),
)
) {
throw new ClusterControlRouteResolutionError(
400,
'invalid_route_query',
'Cluster-control route query is invalid',
);
}
}
}
function matchRoute(
route: CompiledRoute,
method: ClusterControlHttpMethod,
segments: readonly string[],
): ClusterControlRouteParameters | null {
if (route.method !== method || route.segments.length !== segments.length) {
return null;
}
const parameters = Object.create(null) as Record<string, string>;
for (let index = 0; index < segments.length; index += 1) {
const definition = route.segments[index]!;
const value = segments[index]!;
if (definition.kind === 'literal') {
if (definition.value !== value) return null;
} else {
parameters[definition.name] = value;
}
}
return Object.freeze(parameters);
}
/** Returns true only for an object created by the reviewed registry factory. */
export function isClusterControlRouteRegistry(
value: unknown,
): value is ClusterControlRouteRegistry {
return (
!!value &&
typeof value === 'object' &&
reviewedRegistries.has(value as object)
);
}
/**
* Compiles a bounded, immutable and non-overlapping route table. Route-owned
* operation, permission and Project scope are resolved before authentication.
*/
export function createClusterControlRouteRegistry(
definitions: readonly ClusterControlRouteDefinition[],
): ClusterControlRouteRegistry {
if (
!Array.isArray(definitions) ||
definitions.length > CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxRoutes
) {
throw configurationError('definitions must be a bounded array');
}
const routes = definitions.map(compileRoute);
const operationIds = new Set<string>();
for (let index = 0; index < routes.length; index += 1) {
const route = routes[index]!;
if (operationIds.has(route.operationId)) {
throw configurationError('operationId must be unique');
}
operationIds.add(route.operationId);
for (let otherIndex = 0; otherIndex < index; otherIndex += 1) {
if (routesOverlap(route, routes[otherIndex]!)) {
throw configurationError('route definitions overlap');
}
}
}
const registry: ClusterControlRouteRegistry = {
contractVersion: 1,
size: routes.length,
resolve(request) {
const segments = validateRequestPath(request.path);
for (const route of routes) {
const parameters = matchRoute(route, request.method, segments);
if (!parameters) continue;
validateQuery(request.query, route.allowedQuery);
if (route.validateQuery) {
try {
route.validateQuery(request.query);
} catch {
throw new ClusterControlRouteResolutionError(
400,
'invalid_route_query',
'Cluster-control route query is invalid',
);
}
}
const projectId =
route.projectParameter === null
? null
: parameters[route.projectParameter]!;
return Object.freeze({
operationId: route.operationId,
permission: route.permission,
projectId,
handle(request: ClusterControlAuthorizedOperationRequest) {
return route.handle(request, parameters);
},
});
}
return null;
},
};
reviewedRegistries.add(registry);
return Object.freeze(registry);
}