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,800 @@
/** Worker credential management application service boundary. */
import {
PostgresApprovalRequestRepository,
PostgresProjectPolicyRepository,
PostgresWorkerCredentialManagementPlanRepository,
} from '@qinglong/cluster-postgres/worker-credential-manager';
import type { PostgresPool } from '@qinglong/runtime-core';
import {
createApprovalRequest,
normalizeApprovalRequestRecord,
type ApprovalRequestRecord,
type CreateApprovalRequestResult,
type DecideApprovalRequestResult,
} from '@qinglong/runtime-core/approved-action';
import { ProjectPolicyEngine } from '@qinglong/runtime-core/project-policy';
import {
normalizeSecurityPrincipal,
type SecurityPolicyFence,
type SecurityPrincipal,
type SecuritySubject,
} from '@qinglong/runtime-core/security';
import type { SecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
import {
InvalidWorkerCredentialManagementPlanError,
MAX_WORKER_CREDENTIAL_MANAGEMENT_PLAN_LIFETIME_MS,
WorkerCredentialManagementPlanConflictError,
WorkerCredentialManagementPlanUnavailableError,
createWorkerCredentialManagementPlan,
normalizeWorkerCredentialManagementPlan,
type CreateWorkerCredentialManagementPlanResult,
type WorkerCredentialManagementAction,
type WorkerCredentialManagementPlan,
} from '@qinglong/runtime-core/worker-credential-management-plan';
const DEFAULT_APPROVAL_LIFETIME_MS = 15 * 60 * 1000;
const ACTION_REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;
const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const REASON_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
const STRONG_USER_ASSURANCES = new Set([
'multi_factor',
'hardware',
'local_console',
]);
export class WorkerCredentialManagementRequestError extends TypeError {
readonly code = 'WORKER_CREDENTIAL_MANAGEMENT_REQUEST_INVALID';
constructor(message: string) {
super(`Worker credential management request is invalid: ${message}`);
this.name = 'WorkerCredentialManagementRequestError';
}
}
export class WorkerCredentialManagementAuthorizationError extends Error {
readonly code = 'WORKER_CREDENTIAL_MANAGEMENT_FORBIDDEN';
constructor() {
super('Worker credential management is not authorized');
this.name = 'WorkerCredentialManagementAuthorizationError';
}
}
export class WorkerCredentialManagementConflictError extends Error {
readonly code = 'WORKER_CREDENTIAL_MANAGEMENT_CONFLICT';
constructor(message: string) {
super(
`Worker credential management conflicts with durable state: ${message}`,
);
this.name = 'WorkerCredentialManagementConflictError';
}
}
export class WorkerCredentialManagementUnavailableError extends Error {
readonly code = 'WORKER_CREDENTIAL_MANAGEMENT_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('Worker credential management is unavailable', options);
this.name = 'WorkerCredentialManagementUnavailableError';
}
}
export class WorkerCredentialManagementQuotaExceededError extends Error {
readonly code = 'WORKER_CREDENTIAL_MANAGEMENT_QUOTA_EXCEEDED';
constructor(readonly retryAfterMs: number) {
super('Worker credential management quota is exceeded');
this.name = 'WorkerCredentialManagementQuotaExceededError';
}
}
export type WorkerCredentialManagementQuotaOperation =
| 'worker-credential.plan'
| 'worker-credential.propose'
| 'worker-credential.decide'
| 'worker-credential.inspect';
export interface WorkerCredentialManagementQuotaPort {
consume(
command: Readonly<{
projectId: string;
subject: Readonly<SecuritySubject>;
operation: WorkerCredentialManagementQuotaOperation;
idempotencyKey: string;
}>,
): Promise<
Readonly<{
admitted: boolean;
retryAfterMs: number | null;
}>
>;
}
export interface PlanClusterWorkerCredentialRequest {
readonly actionRef: string;
readonly authorityProjectId: string;
readonly action: WorkerCredentialManagementAction;
readonly deliveryId: string;
readonly workerId: string;
readonly credentialId: string;
readonly previousCredentialId: string | null;
readonly credentialNotBeforeAtMs: number;
readonly credentialExpiresAtMs: number;
readonly deploymentTargetDigest: string;
readonly deploymentGeneration: string;
readonly principal: SecurityPrincipal;
}
export interface ProposeClusterWorkerCredentialRequest {
readonly actionRef: string;
readonly authorityProjectId: string;
readonly approvalRequestId: string;
readonly approvalAuditEventId: string;
readonly principal: SecurityPrincipal;
}
export interface ProposeClusterWorkerCredentialResult {
readonly plan: Readonly<WorkerCredentialManagementPlan>;
readonly approvalStatus: CreateApprovalRequestResult['status'];
readonly approvalRequest: Readonly<ApprovalRequestRecord>;
}
export interface DecideClusterWorkerCredentialRequest {
readonly actionRef: string;
readonly authorityProjectId: string;
readonly approvalRequestId: string;
readonly expectedVersion: number;
readonly decisionId: string;
readonly auditEventId: string;
readonly decision: 'approved' | 'rejected';
readonly reasonCode: string;
readonly principal: SecurityPrincipal;
}
export interface InspectClusterWorkerCredentialRequest {
readonly actionRef: string;
readonly authorityProjectId: string;
readonly approvalRequestId: string;
readonly inspectionId: string;
readonly principal: SecurityPrincipal;
}
export interface InspectClusterWorkerCredentialResult {
readonly plan: Readonly<WorkerCredentialManagementPlan> | null;
readonly approvalRequest: Readonly<ApprovalRequestRecord> | null;
readonly stale: boolean;
}
export interface ClusterWorkerCredentialManagementService {
plan(
request: PlanClusterWorkerCredentialRequest,
): Promise<Readonly<CreateWorkerCredentialManagementPlanResult>>;
propose(
request: ProposeClusterWorkerCredentialRequest,
): Promise<Readonly<ProposeClusterWorkerCredentialResult>>;
decide(
request: DecideClusterWorkerCredentialRequest,
): Promise<Readonly<DecideApprovalRequestResult>>;
inspectAuthorized(
request: InspectClusterWorkerCredentialRequest,
): Promise<Readonly<InspectClusterWorkerCredentialResult>>;
}
export interface ClusterWorkerCredentialManagementOptions {
readonly pool: PostgresPool;
readonly quota?: WorkerCredentialManagementQuotaPort;
readonly now?: () => number;
readonly planLifetimeMs?: number;
readonly approvalLifetimeMs?: number;
}
function exact(value: unknown, keys: readonly string[], label: string): void {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new WorkerCredentialManagementRequestError(
`${label} must be an object`,
);
}
const actual = Object.keys(value).sort();
const expected = [...keys].sort();
if (
actual.length !== expected.length ||
actual.some((key, index) => key !== expected[index])
) {
throw new WorkerCredentialManagementRequestError(
`${label} shape is invalid`,
);
}
}
function identifier(value: unknown, label: string): string {
if (typeof value !== 'string' || !IDENTIFIER_PATTERN.test(value)) {
throw new WorkerCredentialManagementRequestError(`${label} is invalid`);
}
return value;
}
function actionRef(value: unknown): string {
if (typeof value !== 'string' || !ACTION_REF_PATTERN.test(value)) {
throw new WorkerCredentialManagementRequestError('actionRef is invalid');
}
return value;
}
function currentTime(now: () => number): number {
const value = now();
if (!Number.isSafeInteger(value) || value < 0) {
throw new WorkerCredentialManagementUnavailableError();
}
return value;
}
function same(left: unknown, right: unknown): boolean {
return JSON.stringify(left) === JSON.stringify(right);
}
function sameSubject(
left: Readonly<SecuritySubject>,
right: Readonly<SecuritySubject>,
): boolean {
return left.type === right.type && left.id === right.id;
}
function binding(plan: Readonly<WorkerCredentialManagementPlan>) {
return Object.freeze({
permission: 'worker.manage' as const,
actionType: `worker_credential.delivery.${plan.action}`,
actionRef: plan.actionRef,
actionDigest: plan.planDigest,
previewDigest: plan.previewDigest,
});
}
function audit(
eventId: string,
requestId: string,
operationId: 'approval.request' | 'approval.decide',
projectId: string,
principal: Readonly<SecurityPrincipal>,
outcome: 'allowed' | 'approval_required',
fence: Readonly<SecurityPolicyFence>,
occurredAtMs: number,
): Readonly<SecurityAuditRecord> {
return Object.freeze({
eventId,
requestId,
operationId,
projectId,
subject: principal.subject,
authenticationId: principal.authenticationId,
outcome,
reasons: Object.freeze(['worker_credential_review']),
fence,
occurredAtMs,
});
}
export function createClusterWorkerCredentialManagementService(
options: ClusterWorkerCredentialManagementOptions,
): Readonly<ClusterWorkerCredentialManagementService> {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).some(
(key) =>
key !== 'approvalLifetimeMs' &&
key !== 'now' &&
key !== 'planLifetimeMs' &&
key !== 'pool' &&
key !== 'quota',
)
) {
throw new WorkerCredentialManagementRequestError(
'options shape is invalid',
);
}
if (!options.pool || typeof options.pool.query !== 'function') {
throw new WorkerCredentialManagementRequestError('pool is invalid');
}
if (options.now !== undefined && typeof options.now !== 'function') {
throw new WorkerCredentialManagementRequestError('now is invalid');
}
if (
options.quota !== undefined &&
(!options.quota || typeof options.quota.consume !== 'function')
) {
throw new WorkerCredentialManagementRequestError('quota is invalid');
}
const planLifetimeMs =
options.planLifetimeMs ?? MAX_WORKER_CREDENTIAL_MANAGEMENT_PLAN_LIFETIME_MS;
const approvalLifetimeMs =
options.approvalLifetimeMs ?? DEFAULT_APPROVAL_LIFETIME_MS;
if (
!Number.isSafeInteger(planLifetimeMs) ||
planLifetimeMs < 1_000 ||
planLifetimeMs > MAX_WORKER_CREDENTIAL_MANAGEMENT_PLAN_LIFETIME_MS ||
!Number.isSafeInteger(approvalLifetimeMs) ||
approvalLifetimeMs < 1_000 ||
approvalLifetimeMs > DEFAULT_APPROVAL_LIFETIME_MS
) {
throw new WorkerCredentialManagementRequestError('lifetime is invalid');
}
const now = options.now ?? Date.now;
const plans = new PostgresWorkerCredentialManagementPlanRepository(
options.pool,
);
const approvals = new PostgresApprovalRequestRepository(options.pool);
const policy = new ProjectPolicyEngine(
new PostgresProjectPolicyRepository(options.pool),
);
const consumeQuota = async (
projectId: string,
principal: Readonly<SecurityPrincipal>,
operation: WorkerCredentialManagementQuotaOperation,
idempotencyKey: string,
): Promise<void> => {
if (!options.quota) return;
try {
const result = await options.quota.consume({
projectId,
subject: principal.subject,
operation,
idempotencyKey,
});
if (
!result ||
typeof result !== 'object' ||
typeof result.admitted !== 'boolean' ||
(result.retryAfterMs !== null &&
(!Number.isSafeInteger(result.retryAfterMs) ||
result.retryAfterMs < 1))
) {
throw new Error('quota result is invalid');
}
if (!result.admitted) {
if (result.retryAfterMs === null) {
throw new Error('quota rejection has no retry bound');
}
throw new WorkerCredentialManagementQuotaExceededError(
result.retryAfterMs,
);
}
} catch (error) {
if (error instanceof WorkerCredentialManagementQuotaExceededError) {
throw error;
}
throw new WorkerCredentialManagementUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
};
const authorize = async (
principalValue: SecurityPrincipal,
projectId: string,
permission: 'worker.manage' | 'approval.decide',
observedAtMs: number,
): Promise<
Readonly<{
principal: Readonly<SecurityPrincipal>;
fence: Readonly<SecurityPolicyFence>;
}>
> => {
let principal: Readonly<SecurityPrincipal>;
try {
principal = normalizeSecurityPrincipal(principalValue, observedAtMs);
} catch {
throw new WorkerCredentialManagementAuthorizationError();
}
if (
principal.subject.type !== 'user' ||
!STRONG_USER_ASSURANCES.has(principal.assurance)
) {
throw new WorkerCredentialManagementAuthorizationError();
}
let decision;
try {
decision = await policy.authorize(principal, projectId, permission);
} catch (error) {
throw new WorkerCredentialManagementUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
if (decision.effect !== 'allow' || decision.fence === null) {
throw new WorkerCredentialManagementAuthorizationError();
}
return Object.freeze({ principal, fence: decision.fence });
};
const loadPlan = async (
requestedActionRef: string,
): Promise<Readonly<WorkerCredentialManagementPlan>> => {
let value;
try {
value = await plans.findByActionRef(actionRef(requestedActionRef));
} catch (error) {
throw new WorkerCredentialManagementUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
if (!value) {
throw new WorkerCredentialManagementConflictError('plan does not exist');
}
return normalizeWorkerCredentialManagementPlan(value);
};
return Object.freeze({
async plan(request: PlanClusterWorkerCredentialRequest) {
exact(
request,
[
'action',
'actionRef',
'authorityProjectId',
'credentialExpiresAtMs',
'credentialId',
'credentialNotBeforeAtMs',
'deliveryId',
'deploymentGeneration',
'deploymentTargetDigest',
'previousCredentialId',
'principal',
'workerId',
],
'plan request',
);
const observedAtMs = currentTime(now);
const authorization = await authorize(
request.principal,
identifier(request.authorityProjectId, 'authorityProjectId'),
'worker.manage',
observedAtMs,
);
await consumeQuota(
request.authorityProjectId,
authorization.principal,
'worker-credential.plan',
actionRef(request.actionRef),
);
try {
const plan = createWorkerCredentialManagementPlan({
actionRef: actionRef(request.actionRef),
authorityProjectId: request.authorityProjectId,
action: request.action,
target: {
deliveryId: request.deliveryId,
workerId: request.workerId,
credentialId: request.credentialId,
previousCredentialId: request.previousCredentialId,
credentialNotBeforeAtMs: request.credentialNotBeforeAtMs,
credentialExpiresAtMs: request.credentialExpiresAtMs,
deploymentTargetDigest: request.deploymentTargetDigest,
deploymentGeneration: request.deploymentGeneration,
},
requestedBy: authorization.principal.subject,
plannedAtMs: observedAtMs,
expiresAtMs: observedAtMs + planLifetimeMs,
});
return await plans.create(plan);
} catch (error) {
if (error instanceof InvalidWorkerCredentialManagementPlanError) {
throw new WorkerCredentialManagementRequestError('plan is invalid');
}
if (error instanceof WorkerCredentialManagementPlanConflictError) {
throw new WorkerCredentialManagementConflictError(
'plan identity is already bound',
);
}
throw new WorkerCredentialManagementUnavailableError({
cause:
error instanceof WorkerCredentialManagementPlanUnavailableError
? error
: error instanceof Error
? error
: undefined,
});
}
},
async propose(request: ProposeClusterWorkerCredentialRequest) {
exact(
request,
[
'actionRef',
'authorityProjectId',
'approvalAuditEventId',
'approvalRequestId',
'principal',
],
'proposal request',
);
const approvalRequestId = identifier(
request.approvalRequestId,
'approvalRequestId',
);
const approvalAuditEventId = identifier(
request.approvalAuditEventId,
'approvalAuditEventId',
);
const observedAtMs = currentTime(now);
const authorityProjectId = identifier(
request.authorityProjectId,
'authorityProjectId',
);
const authorization = await authorize(
request.principal,
authorityProjectId,
'worker.manage',
observedAtMs,
);
await consumeQuota(
authorityProjectId,
authorization.principal,
'worker-credential.propose',
approvalRequestId,
);
const plan = await loadPlan(request.actionRef);
if (plan.authorityProjectId !== authorityProjectId) {
throw new WorkerCredentialManagementConflictError(
'plan belongs to another authority Project',
);
}
if (observedAtMs > plan.expiresAtMs) {
throw new WorkerCredentialManagementConflictError('plan expired');
}
if (!sameSubject(plan.requestedBy, authorization.principal.subject)) {
throw new WorkerCredentialManagementAuthorizationError();
}
const action = binding(plan);
let existing;
try {
existing = await approvals.findById(approvalRequestId);
} catch (error) {
throw new WorkerCredentialManagementUnavailableError({
cause: error instanceof Error ? error : undefined,
});
}
if (existing) {
const normalized = normalizeApprovalRequestRecord(existing);
if (
normalized.projectId !== plan.authorityProjectId ||
normalized.decisionMode !== 'separation_of_duty' ||
!sameSubject(normalized.requestedBy, plan.requestedBy) ||
!same(normalized.action, action)
) {
throw new WorkerCredentialManagementConflictError(
'approval is bound to another plan',
);
}
return Object.freeze({
plan,
approvalStatus: 'existing' as const,
approvalRequest: normalized,
});
}
const expiresAtMs = Math.min(
observedAtMs + approvalLifetimeMs,
plan.expiresAtMs,
);
if (expiresAtMs <= observedAtMs) {
throw new WorkerCredentialManagementConflictError(
'plan has no approval lifetime',
);
}
const created = await approvals.create({
request: createApprovalRequest({
id: approvalRequestId,
projectId: plan.authorityProjectId,
action,
risk: 'high',
decisionMode: 'separation_of_duty',
requestedBy: authorization.principal.subject,
requestedAtMs: observedAtMs,
expiresAtMs,
requestFence: authorization.fence,
}),
audit: audit(
approvalAuditEventId,
approvalRequestId,
'approval.request',
plan.authorityProjectId,
authorization.principal,
'approval_required',
authorization.fence,
observedAtMs,
),
});
return Object.freeze({
plan,
approvalStatus: created.status,
approvalRequest: created.request,
});
},
async decide(request: DecideClusterWorkerCredentialRequest) {
exact(
request,
[
'actionRef',
'authorityProjectId',
'approvalRequestId',
'auditEventId',
'decision',
'decisionId',
'expectedVersion',
'principal',
'reasonCode',
],
'decision request',
);
if (
(request.decision !== 'approved' && request.decision !== 'rejected') ||
typeof request.reasonCode !== 'string' ||
!REASON_PATTERN.test(request.reasonCode) ||
!Number.isSafeInteger(request.expectedVersion) ||
request.expectedVersion < 1
) {
throw new WorkerCredentialManagementRequestError(
'decision tuple is invalid',
);
}
const approvalRequestId = identifier(
request.approvalRequestId,
'approvalRequestId',
);
const observedAtMs = currentTime(now);
const authorityProjectId = identifier(
request.authorityProjectId,
'authorityProjectId',
);
const authorization = await authorize(
request.principal,
authorityProjectId,
'approval.decide',
observedAtMs,
);
const decisionId = identifier(request.decisionId, 'decisionId');
await consumeQuota(
authorityProjectId,
authorization.principal,
'worker-credential.decide',
decisionId,
);
const plan = await loadPlan(request.actionRef);
if (plan.authorityProjectId !== authorityProjectId) {
throw new WorkerCredentialManagementConflictError(
'plan belongs to another authority Project',
);
}
const current = await approvals.findById(approvalRequestId);
if (!current) {
throw new WorkerCredentialManagementConflictError(
'approval does not exist',
);
}
const approval = normalizeApprovalRequestRecord(current);
if (
approval.projectId !== plan.authorityProjectId ||
!same(approval.action, binding(plan))
) {
throw new WorkerCredentialManagementConflictError(
'approval does not match plan',
);
}
if (
approval.decisionId === decisionId &&
approval.decision === request.decision &&
approval.decisionReasonCode === request.reasonCode &&
approval.decidedBy &&
sameSubject(approval.decidedBy, authorization.principal.subject)
) {
return Object.freeze({
status: 'existing' as const,
request: approval,
});
}
return approvals.decide({
requestId: approvalRequestId,
expectedVersion: request.expectedVersion,
decisionId,
decision: request.decision,
reasonCode: request.reasonCode,
principal: authorization.principal,
decidedAtMs: observedAtMs,
authorizationFence: authorization.fence,
audit: audit(
identifier(request.auditEventId, 'auditEventId'),
approvalRequestId,
'approval.decide',
approval.projectId,
authorization.principal,
'allowed',
authorization.fence,
observedAtMs,
),
});
},
async inspectAuthorized(request: InspectClusterWorkerCredentialRequest) {
exact(
request,
[
'actionRef',
'authorityProjectId',
'approvalRequestId',
'inspectionId',
'principal',
],
'inspection request',
);
const inspectionId = identifier(request.inspectionId, 'inspectionId');
const projectId = identifier(
request.authorityProjectId,
'authorityProjectId',
);
const observedAtMs = currentTime(now);
let authorization;
try {
authorization = await authorize(
request.principal,
projectId,
'worker.manage',
observedAtMs,
);
} catch (error) {
if (!(error instanceof WorkerCredentialManagementAuthorizationError)) {
throw error;
}
authorization = await authorize(
request.principal,
projectId,
'approval.decide',
observedAtMs,
);
}
await consumeQuota(
projectId,
authorization.principal,
'worker-credential.inspect',
inspectionId,
);
const [planValue, approvalValue] = await Promise.all([
plans.findByActionRef(actionRef(request.actionRef)),
approvals.findById(
identifier(request.approvalRequestId, 'approvalRequestId'),
),
]);
if (!planValue && !approvalValue) {
throw new WorkerCredentialManagementConflictError(
'management state does not exist',
);
}
const plan = planValue
? normalizeWorkerCredentialManagementPlan(planValue)
: null;
const approval = approvalValue
? normalizeApprovalRequestRecord(approvalValue)
: null;
if (
(plan && plan.authorityProjectId !== projectId) ||
(approval && approval.projectId !== projectId)
) {
throw new WorkerCredentialManagementConflictError(
'management state belongs to another authority Project',
);
}
return Object.freeze({
plan,
approvalRequest: approval,
stale:
plan === null ||
approval === null ||
approval.projectId !== plan.authorityProjectId ||
!same(approval.action, binding(plan)) ||
observedAtMs > plan.expiresAtMs,
});
},
});
}
@@ -0,0 +1,111 @@
#!/usr/bin/env node
/** Worker credential management service CLI boundary. */
import {
startClusterWorkerCredentialManagementProcess,
type ClusterWorkerCredentialManagementProcessRuntime,
} from './workerCredentialManagementProcess';
const USAGE = 'Usage: ql3-worker-credential-manage';
function failureFact(error: unknown): Readonly<Record<string, unknown>> {
const candidate = error as {
readonly name?: unknown;
readonly code?: unknown;
};
return Object.freeze({
schemaVersion: 1,
component: 'qinglong3-worker-credential-management',
event: 'management_failed',
name:
typeof candidate?.name === 'string' && candidate.name.length <= 128
? candidate.name
: 'Error',
...(typeof candidate?.code === 'string' && candidate.code.length <= 128
? { code: candidate.code }
: {}),
});
}
function emit(value: Readonly<Record<string, unknown>>): void {
process.stdout.write(`${JSON.stringify(value)}\n`);
}
async function run(argv: readonly string[]): Promise<void> {
if (argv.length === 1 && (argv[0] === '--help' || argv[0] === '-h')) {
process.stdout.write(`${USAGE}\n`);
return;
}
if (argv.length !== 0) {
process.stderr.write(
`${JSON.stringify({
code: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_CLI_USAGE_INVALID',
message: USAGE,
})}\n`,
);
process.exitCode = 64;
return;
}
let runtime: Readonly<ClusterWorkerCredentialManagementProcessRuntime>;
try {
runtime = await startClusterWorkerCredentialManagementProcess({
environment: process.env,
onError() {
emit({
schemaVersion: 1,
component: 'qinglong3-worker-credential-management',
event: 'management_unavailable',
});
},
});
} catch (error) {
process.stderr.write(`${JSON.stringify(failureFact(error))}\n`);
process.exitCode = 1;
return;
}
if (runtime.status === 'disabled') {
emit({
schemaVersion: 1,
component: 'qinglong3-worker-credential-management',
event: 'management_disabled',
});
return;
}
emit({
schemaVersion: 1,
component: 'qinglong3-worker-credential-management',
event: 'management_started',
address: runtime.address,
identityGeneration: runtime.identity.generation,
databaseContractVersion: runtime.database.contractVersion,
databaseMigrationCount: runtime.database.migrationIds.length,
});
let stopping: Promise<void> | undefined;
const stop = (): Promise<void> => {
stopping ??= runtime.close().then(() => {
emit({
schemaVersion: 1,
component: 'qinglong3-worker-credential-management',
event: 'management_stopped',
});
});
return stopping;
};
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
process.once(signal, () => {
void stop().then(
() => {
process.exitCode = 0;
},
(error) => {
process.stderr.write(`${JSON.stringify(failureFact(error))}\n`);
process.exitCode = 1;
},
);
});
}
}
void run(process.argv.slice(2));
@@ -0,0 +1,46 @@
/** TLS 1.3 Worker credential management HTTP adapter boundary. */
import {
CLUSTER_WORKER_CREDENTIAL_MANAGEMENT_PATH,
startClusterPluginPackageManagementHttp,
type ClusterPluginPackageManagementHttpApplication,
type ClusterPluginPackageManagementHttpLimits,
} from '../../management-support/pluginPackageManagementHttp';
import type { ClusterPluginPackageIdentityKeysetFile } from '../../management-support/pluginPackageIdentityKeyset';
import type { ClusterWorkerCredentialManagementTransport } from './workerCredentialManagementTransport';
export type ClusterWorkerCredentialManagementHttpLimits =
ClusterPluginPackageManagementHttpLimits;
export type ClusterWorkerCredentialManagementHttpApplication =
ClusterPluginPackageManagementHttpApplication;
export interface StartClusterWorkerCredentialManagementHttpOptions {
readonly host: string;
readonly port: number;
readonly tls: Readonly<{
readonly privateKey: Buffer;
readonly certificate: Buffer;
readonly clientCertificateAuthority: Buffer;
readonly clientCertificateRevocationList: Buffer;
}>;
readonly transport: ClusterWorkerCredentialManagementTransport;
readonly identities: ClusterPluginPackageIdentityKeysetFile;
readonly limits?: ClusterWorkerCredentialManagementHttpLimits;
readonly now?: () => number;
readonly createRequestId?: () => string;
readonly onError?: (error: unknown) => void;
}
/**
* Starts the Worker credential management endpoint on the shared Cluster Admin
* TLS 1.3/OIDC boundary. The public manager process never receives credential
* delivery or Kubernetes execution capabilities.
*/
export async function startClusterWorkerCredentialManagementHttp(
options: StartClusterWorkerCredentialManagementHttpOptions,
): Promise<Readonly<ClusterWorkerCredentialManagementHttpApplication>> {
return startClusterPluginPackageManagementHttp({
...options,
managementPath: CLUSTER_WORKER_CREDENTIAL_MANAGEMENT_PATH,
});
}
@@ -0,0 +1,132 @@
/** Shared Cluster management mutual-TLS trust validation boundary. */
import { createHash, X509Certificate } from 'node:crypto';
import { createSecureContext } from 'node:tls';
import { TextDecoder } from 'node:util';
import type { ClusterManagementProcessConfigurationFailure } from '../../management-support/managementProcessSupport';
const MAX_PEM_BLOCKS = 16;
const STRICT_UTF8 = new TextDecoder('utf-8', { fatal: true });
function exactPemBlocks(
bytes: Buffer,
label: 'CERTIFICATE' | 'X509 CRL',
description: string,
failure: ClusterManagementProcessConfigurationFailure,
): readonly Buffer[] {
let value: string;
try {
value = STRICT_UTF8.decode(bytes);
} catch {
throw failure(`${description} bundle must be strict UTF-8`);
}
const pattern = new RegExp(
`-----BEGIN ${label}-----[\\s\\S]*?-----END ${label}-----`,
'g',
);
const matches = value.match(pattern);
if (!matches || matches.length < 1 || matches.length > MAX_PEM_BLOCKS) {
throw failure(
`${description} bundle must contain 1 to ${MAX_PEM_BLOCKS} PEM blocks`,
);
}
if (value.replace(pattern, '').trim() !== '') {
throw failure(`${description} bundle contains unsupported data`);
}
return Object.freeze(
matches.map((match) => Buffer.from(`${match}\n`, 'utf8')),
);
}
function validateCertificateAuthorities(
authorities: readonly Buffer[],
now: number,
failure: ClusterManagementProcessConfigurationFailure,
): void {
const fingerprints = new Set<string>();
for (const authorityBytes of authorities) {
let authority: X509Certificate;
try {
authority = new X509Certificate(authorityBytes);
} catch {
throw failure('client certificate authority is not an X.509 certificate');
}
const validFrom = Date.parse(authority.validFrom);
const validTo = Date.parse(authority.validTo);
if (
!Number.isFinite(validFrom) ||
!Number.isFinite(validTo) ||
now < validFrom ||
now >= validTo
) {
throw failure('client certificate authority is not currently valid');
}
if (!authority.ca) {
throw failure('client certificate authority is not a CA');
}
if (fingerprints.has(authority.fingerprint256)) {
throw failure('client certificate authority bundle contains a duplicate');
}
fingerprints.add(authority.fingerprint256);
}
}
function rejectDuplicateRevocationLists(
revocationLists: readonly Buffer[],
failure: ClusterManagementProcessConfigurationFailure,
): void {
const digests = new Set<string>();
for (const revocationList of revocationLists) {
const digest = createHash('sha256').update(revocationList).digest('hex');
if (digests.has(digest)) {
throw failure(
'client certificate revocation list bundle contains a duplicate',
);
}
digests.add(digest);
}
}
export function validateWorkerCredentialManagementClientTrust(
certificateAuthorityBundle: Buffer,
certificateRevocationListBundle: Buffer,
now: number,
failure: ClusterManagementProcessConfigurationFailure,
): void {
if (!Number.isSafeInteger(now) || now < 0) {
throw failure('TLS observation time is invalid');
}
const authorities = exactPemBlocks(
certificateAuthorityBundle,
'CERTIFICATE',
'client certificate authority',
failure,
);
let revocationLists: readonly Buffer[] = Object.freeze([]);
try {
revocationLists = exactPemBlocks(
certificateRevocationListBundle,
'X509 CRL',
'client certificate revocation list',
failure,
);
validateCertificateAuthorities(authorities, now, failure);
rejectDuplicateRevocationLists(revocationLists, failure);
try {
createSecureContext({
ca: [...authorities],
crl: [...revocationLists],
minVersion: 'TLSv1.3',
maxVersion: 'TLSv1.3',
});
} catch {
throw failure('client trust or revocation bundle is invalid');
}
} finally {
for (const authority of authorities) authority.fill(0);
for (const revocationList of revocationLists) revocationList.fill(0);
}
}
export const validateClusterManagementClientTrust =
validateWorkerCredentialManagementClientTrust;
@@ -0,0 +1,673 @@
/** Worker credential management PostgreSQL process composition boundary. */
import type {
OpenPostgresDatabase,
PostgresDatabaseResource,
} from '@qinglong/runtime-core';
import {
assertPostgresWorkerCredentialManagerSchemaReady,
createPostgresDatabaseOpener,
isPostgresTlsDnsServername,
loadPostgresCertificateAuthorityFile,
loadPostgresConnectionEnvironment,
PostgresWorkerCredentialManagementIdentityKeysetLedgerRepository,
PostgresWorkerCredentialManagementQuotaRepository,
type PostgresConnectionOptions,
type PostgresPoolOptions,
type PostgresSchemaReadinessReport,
} from '@qinglong/cluster-postgres/worker-credential-manager';
import {
absoluteManagementEnvironmentFile,
booleanManagementEnvironmentValue,
boundedManagementEnvironmentValue,
integerManagementEnvironmentValue,
readManagementTlsFile,
} from '../../management-support/managementProcessSupport';
import {
createClusterWorkerCredentialIdentityKeysetFile,
type ClusterPluginPackageIdentityKeysetFile,
type ClusterPluginPackageIdentityKeysetSnapshot,
} from '../../management-support/pluginPackageIdentityKeyset';
import { createClusterWorkerCredentialManagementService } from './workerCredentialManagement';
import {
startClusterWorkerCredentialManagementHttp,
type ClusterWorkerCredentialManagementHttpApplication,
type StartClusterWorkerCredentialManagementHttpOptions,
} from './workerCredentialManagementHttp';
import { createClusterWorkerCredentialManagementTransport } from './workerCredentialManagementTransport';
import { validateWorkerCredentialManagementClientTrust } from './workerCredentialManagementMutualTls';
const SAFE_HOST = /^[A-Za-z0-9][A-Za-z0-9.:-]{0,254}$/;
const SAFE_APPLICATION_NAME = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,62}$/;
export type ClusterWorkerCredentialManagementProcessEnvironment = Readonly<
Record<string, string | undefined>
>;
export type ClusterWorkerCredentialManagementProcessConfig =
| Readonly<{ enabled: false }>
| Readonly<{
enabled: true;
profile: 'cluster-admin';
host: string;
port: number;
certificateFile: string;
privateKeyFile: string;
clientCertificateAuthorityFile: string;
clientCertificateRevocationListFile: string;
identityKeysetFile: string;
planLifetimeMs: number;
approvalLifetimeMs: number;
quota: Readonly<{
windowMs: number;
planLimit: number;
proposeLimit: number;
decideLimit: number;
inspectLimit: number;
}>;
http: Readonly<{
maxBodyBytes: number;
maxConnections: number;
maxConcurrentRequests: number;
requestTimeoutMs: number;
drainTimeoutMs: number;
rateWindowMs: number;
peerRequestLimit: number;
globalRequestLimit: number;
maxRateLimitPeers: number;
}>;
database: Readonly<{
connection: PostgresConnectionOptions;
pool: PostgresPoolOptions;
}>;
}>;
export type ClusterWorkerCredentialManagementProcessRuntime =
| Readonly<{
status: 'disabled';
close(): Promise<void>;
}>
| Readonly<{
status: 'active';
address: Readonly<{ host: string; port: number }>;
database: PostgresSchemaReadinessReport;
identity: ClusterPluginPackageIdentityKeysetSnapshot;
availabilityStatus(): 'ready' | 'unavailable' | 'stopped';
close(): Promise<void>;
}>;
export interface StartClusterWorkerCredentialManagementProcessOptions {
readonly environment: ClusterWorkerCredentialManagementProcessEnvironment;
readonly openDatabase?: OpenPostgresDatabase;
readonly identities?: ClusterPluginPackageIdentityKeysetFile;
readonly assertReady?: (
pool: PostgresDatabaseResource['pool'],
) => Promise<PostgresSchemaReadinessReport>;
readonly startHttp?: (
options: StartClusterWorkerCredentialManagementHttpOptions,
) => Promise<Readonly<ClusterWorkerCredentialManagementHttpApplication>>;
readonly now?: () => number;
readonly onError?: (error: unknown) => void;
}
export class ClusterWorkerCredentialManagementProcessConfigError extends TypeError {
readonly code = 'QL3_WORKER_CREDENTIAL_MANAGEMENT_PROCESS_CONFIG_INVALID';
constructor(message: string) {
super(
`Worker credential management process configuration is invalid: ${message}`,
);
this.name = 'ClusterWorkerCredentialManagementProcessConfigError';
}
}
function configFailure(
message: string,
): ClusterWorkerCredentialManagementProcessConfigError {
return new ClusterWorkerCredentialManagementProcessConfigError(message);
}
function boundedValue(
environment: ClusterWorkerCredentialManagementProcessEnvironment,
name: string,
maximumLength: number,
required = false,
): string | undefined {
return boundedManagementEnvironmentValue(
environment,
name,
maximumLength,
configFailure,
required,
);
}
function booleanValue(
environment: ClusterWorkerCredentialManagementProcessEnvironment,
name: string,
): boolean {
return booleanManagementEnvironmentValue(environment, name, configFailure);
}
function integerValue(
environment: ClusterWorkerCredentialManagementProcessEnvironment,
name: string,
fallback: number,
minimum: number,
maximum: number,
): number {
return integerManagementEnvironmentValue(
environment,
name,
fallback,
minimum,
maximum,
configFailure,
);
}
function absoluteFile(
environment: ClusterWorkerCredentialManagementProcessEnvironment,
name: string,
): string {
return absoluteManagementEnvironmentFile(environment, name, configFailure);
}
function loadConnection(
environment: ClusterWorkerCredentialManagementProcessEnvironment,
): Readonly<{
connection: PostgresConnectionOptions;
pool: PostgresPoolOptions;
}> {
let connection: PostgresConnectionOptions;
try {
connection = loadPostgresConnectionEnvironment(environment, {
connectionString: 'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_URL',
host: 'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_HOST',
port: 'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_PORT',
database: 'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_DATABASE',
user: 'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_USER',
password: 'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_PASSWORD',
});
} catch (error) {
throw configFailure(
error instanceof Error
? error.message
: 'PostgreSQL Worker credential manager connection is invalid',
);
}
const mode =
environment.QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_TLS_MODE ??
'verify-full';
if (mode !== 'verify-full' && mode !== 'disable') {
throw configFailure(
'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_TLS_MODE must be verify-full or disable',
);
}
if (
mode === 'disable' &&
!booleanValue(
environment,
'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_ALLOW_INSECURE',
)
) {
throw configFailure(
'disabling Worker credential manager PostgreSQL TLS requires QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_ALLOW_INSECURE=true',
);
}
const servername = boundedValue(
environment,
'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_TLS_SERVERNAME',
253,
);
if (mode === 'verify-full' && !isPostgresTlsDnsServername(servername)) {
throw configFailure(
'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_TLS_SERVERNAME must be an explicit DNS name',
);
}
const caFile = boundedValue(
environment,
'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_TLS_CA_FILE',
4_096,
);
if (mode === 'disable' && caFile !== undefined) {
throw configFailure(
'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_TLS_CA_FILE cannot be used when TLS is disabled',
);
}
let ca: string | undefined;
if (caFile !== undefined) {
try {
ca = loadPostgresCertificateAuthorityFile(caFile);
} catch {
throw configFailure(
'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_TLS_CA_FILE is invalid',
);
}
}
const applicationName =
boundedValue(
environment,
'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_APPLICATION_NAME',
63,
) ?? 'qinglong3-worker-credential-manager';
if (!SAFE_APPLICATION_NAME.test(applicationName)) {
throw configFailure(
'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_APPLICATION_NAME is invalid',
);
}
return Object.freeze({
connection: Object.freeze({
...connection,
tls:
mode === 'disable'
? { mode: 'disable' as const }
: {
mode: 'verify-full' as const,
servername: servername!,
...(ca === undefined ? {} : { ca }),
},
}),
pool: Object.freeze({
applicationName,
maxConnections: integerValue(
environment,
'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_MAX_CONNECTIONS',
2,
1,
4,
),
connectionTimeoutMs: integerValue(
environment,
'QL3_POSTGRES_WORKER_CREDENTIAL_MANAGER_CONNECTION_TIMEOUT_MS',
5_000,
100,
60_000,
),
}),
});
}
export function loadClusterWorkerCredentialManagementProcessConfig(
environment: ClusterWorkerCredentialManagementProcessEnvironment,
): Readonly<ClusterWorkerCredentialManagementProcessConfig> {
if (!environment || typeof environment !== 'object') {
throw configFailure('environment is invalid');
}
if (!booleanValue(environment, 'QL3_WORKER_CREDENTIAL_MANAGEMENT_ENABLED')) {
return Object.freeze({ enabled: false as const });
}
if (environment.QL3_PROFILE !== 'cluster-admin') {
throw configFailure(
'QL3_PROFILE must be cluster-admin when Worker credential management is enabled',
);
}
const host =
boundedValue(environment, 'QL3_WORKER_CREDENTIAL_MANAGEMENT_HOST', 255) ??
'0.0.0.0';
if (!SAFE_HOST.test(host)) {
throw configFailure('QL3_WORKER_CREDENTIAL_MANAGEMENT_HOST is invalid');
}
const http = Object.freeze({
maxBodyBytes: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_MAX_BODY_BYTES',
64 * 1024,
1_024,
256 * 1024,
),
maxConnections: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_MAX_CONNECTIONS',
64,
1,
512,
),
maxConcurrentRequests: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_MAX_CONCURRENT_REQUESTS',
32,
1,
256,
),
requestTimeoutMs: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_REQUEST_TIMEOUT_MS',
10_000,
1_000,
60_000,
),
drainTimeoutMs: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_DRAIN_TIMEOUT_MS',
5_000,
100,
60_000,
),
rateWindowMs: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_RATE_WINDOW_MS',
60_000,
1_000,
5 * 60_000,
),
peerRequestLimit: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_PEER_REQUEST_LIMIT',
60,
1,
10_000,
),
globalRequestLimit: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_GLOBAL_REQUEST_LIMIT',
600,
1,
100_000,
),
maxRateLimitPeers: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_MAX_RATE_LIMIT_PEERS',
1_024,
1,
16_384,
),
});
if (http.globalRequestLimit < http.peerRequestLimit) {
throw configFailure(
'global request limit cannot be below the peer request limit',
);
}
return Object.freeze({
enabled: true as const,
profile: 'cluster-admin' as const,
host,
port: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_PORT',
8_444,
1,
65_535,
),
certificateFile: absoluteFile(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_TLS_CERT_FILE',
),
privateKeyFile: absoluteFile(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_TLS_KEY_FILE',
),
clientCertificateAuthorityFile: absoluteFile(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_CLIENT_CA_FILE',
),
clientCertificateRevocationListFile: absoluteFile(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_CLIENT_CRL_FILE',
),
identityKeysetFile: absoluteFile(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_IDENTITY_KEYSET_FILE',
),
planLifetimeMs: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_PLAN_LIFETIME_MS',
15 * 60_000,
1_000,
15 * 60_000,
),
approvalLifetimeMs: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_APPROVAL_LIFETIME_MS',
15 * 60_000,
1_000,
15 * 60_000,
),
quota: Object.freeze({
windowMs: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_QUOTA_WINDOW_MS',
60_000,
1_000,
5 * 60_000,
),
planLimit: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_PLAN_QUOTA',
30,
1,
1_000,
),
proposeLimit: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_PROPOSE_QUOTA',
30,
1,
1_000,
),
decideLimit: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_DECIDE_QUOTA',
60,
1,
1_000,
),
inspectLimit: integerValue(
environment,
'QL3_WORKER_CREDENTIAL_MANAGEMENT_INSPECT_QUOTA',
600,
1,
1_000,
),
}),
http,
database: loadConnection(environment),
});
}
export async function startClusterWorkerCredentialManagementProcess(
options: StartClusterWorkerCredentialManagementProcessOptions,
): Promise<Readonly<ClusterWorkerCredentialManagementProcessRuntime>> {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).some(
(key) =>
![
'environment',
'openDatabase',
'identities',
'assertReady',
'startHttp',
'now',
'onError',
].includes(key),
) ||
!options.environment ||
typeof options.environment !== 'object' ||
(options.openDatabase !== undefined &&
typeof options.openDatabase !== 'function') ||
(options.identities !== undefined &&
(typeof options.identities.reload !== 'function' ||
typeof options.identities.bind !== 'function')) ||
(options.assertReady !== undefined &&
typeof options.assertReady !== 'function') ||
(options.startHttp !== undefined &&
typeof options.startHttp !== 'function') ||
(options.now !== undefined && typeof options.now !== 'function') ||
(options.onError !== undefined && typeof options.onError !== 'function')
) {
throw configFailure('options are invalid');
}
const config = loadClusterWorkerCredentialManagementProcessConfig(
options.environment,
);
if (!config.enabled) {
return Object.freeze({
status: 'disabled' as const,
close: () => Promise.resolve(),
});
}
const now = options.now ?? Date.now;
let http:
| Readonly<ClusterWorkerCredentialManagementHttpApplication>
| undefined;
let database: PostgresDatabaseResource | undefined;
let unavailableError: unknown;
let closePromise: Promise<void> | undefined;
const report = (error: unknown): void => {
try {
options.onError?.(error);
} catch {
// Diagnostics do not own availability or cleanup.
}
};
const openDatabase =
options.openDatabase ??
createPostgresDatabaseOpener({
role: 'worker-credential-manager',
connection: config.database.connection,
pool: config.database.pool,
onPoolError(error) {
const firstAvailabilityError = unavailableError === undefined;
unavailableError ??= error;
http?.withdraw(error);
if (firstAvailabilityError) report(error);
},
});
try {
database = await openDatabase();
const evidence = await (
options.assertReady ?? assertPostgresWorkerCredentialManagerSchemaReady
)(database.pool);
if (unavailableError !== undefined) throw unavailableError;
const identities =
options.identities ??
createClusterWorkerCredentialIdentityKeysetFile({
filePath: config.identityKeysetFile,
now,
ledger:
new PostgresWorkerCredentialManagementIdentityKeysetLedgerRepository(
database.pool,
'worker-credential-management',
),
});
const identity = await identities.reload();
const quota = new PostgresWorkerCredentialManagementQuotaRepository(
database.pool,
{
windowMs: config.quota.windowMs,
limits: {
'worker-credential.plan': config.quota.planLimit,
'worker-credential.propose': config.quota.proposeLimit,
'worker-credential.decide': config.quota.decideLimit,
'worker-credential.inspect': config.quota.inspectLimit,
},
},
);
const service = createClusterWorkerCredentialManagementService({
pool: database.pool,
planLifetimeMs: config.planLifetimeMs,
approvalLifetimeMs: config.approvalLifetimeMs,
quota,
now,
});
const transport = createClusterWorkerCredentialManagementTransport({
service,
now,
});
const privateKey = readManagementTlsFile(
config.privateKeyFile,
true,
configFailure,
);
try {
const certificate = readManagementTlsFile(
config.certificateFile,
false,
configFailure,
);
const clientCertificateAuthority = readManagementTlsFile(
config.clientCertificateAuthorityFile,
false,
configFailure,
);
const clientCertificateRevocationList = readManagementTlsFile(
config.clientCertificateRevocationListFile,
false,
configFailure,
);
try {
validateWorkerCredentialManagementClientTrust(
clientCertificateAuthority,
clientCertificateRevocationList,
now(),
configFailure,
);
http = await (
options.startHttp ?? startClusterWorkerCredentialManagementHttp
)({
host: config.host,
port: config.port,
tls: {
privateKey,
certificate,
clientCertificateAuthority,
clientCertificateRevocationList,
},
transport,
identities,
limits: config.http,
now,
onError: report,
});
} finally {
clientCertificateAuthority.fill(0);
clientCertificateRevocationList.fill(0);
}
} finally {
privateKey.fill(0);
}
if (unavailableError !== undefined) {
http.withdraw(unavailableError);
}
return Object.freeze({
status: 'active' as const,
address: http.address,
database: evidence,
identity,
availabilityStatus: () => http?.availabilityStatus() ?? 'stopped',
close(): Promise<void> {
if (closePromise) return closePromise;
closePromise = (async () => {
let primaryError: unknown;
try {
await http?.close();
} catch (error) {
primaryError = error;
}
try {
await database?.close();
} catch (error) {
primaryError ??= error;
}
if (primaryError) throw primaryError;
})();
return closePromise;
},
});
} catch (error) {
try {
await http?.close();
} catch {
// Preserve startup failure.
}
try {
await database?.close();
} catch {
// Preserve startup failure.
}
throw error;
}
}
@@ -0,0 +1,426 @@
/** Authenticated Worker credential management transport boundary. */
import type { ApprovalRequestRecord } from '@qinglong/runtime-core/approved-action';
import {
normalizeSecurityPrincipal,
type SecurityPrincipal,
} from '@qinglong/runtime-core/security';
import type {
CreateWorkerCredentialManagementPlanResult,
WorkerCredentialManagementPlan,
} from '@qinglong/runtime-core/worker-credential-management-plan';
import type {
ClusterWorkerCredentialManagementService,
ProposeClusterWorkerCredentialResult,
} from './workerCredentialManagement';
const STRONG_CLUSTER_ASSURANCES = new Set(['multi_factor', 'hardware']);
export interface ClusterWorkerCredentialManagementAuthentication {
authenticate(): Promise<Readonly<SecurityPrincipal> | null>;
}
export interface PlanClusterWorkerCredentialCommand {
readonly schemaVersion: 1;
readonly operation: 'worker-credential.plan';
readonly request: {
readonly actionRef: string;
readonly authorityProjectId: string;
readonly action: 'issue' | 'rotate';
readonly deliveryId: string;
readonly workerId: string;
readonly credentialId: string;
readonly previousCredentialId: string | null;
readonly credentialNotBeforeAtMs: number;
readonly credentialExpiresAtMs: number;
readonly deploymentTargetDigest: string;
readonly deploymentGeneration: string;
};
}
export interface ProposeClusterWorkerCredentialCommand {
readonly schemaVersion: 1;
readonly operation: 'worker-credential.propose';
readonly request: {
readonly actionRef: string;
readonly authorityProjectId: string;
readonly approvalRequestId: string;
readonly approvalAuditEventId: string;
};
}
export interface DecideClusterWorkerCredentialCommand {
readonly schemaVersion: 1;
readonly operation: 'worker-credential.decide';
readonly request: {
readonly actionRef: string;
readonly authorityProjectId: string;
readonly approvalRequestId: string;
readonly expectedVersion: number;
readonly decisionId: string;
readonly auditEventId: string;
readonly decision: 'approved' | 'rejected';
readonly reasonCode: string;
};
}
export interface InspectClusterWorkerCredentialCommand {
readonly schemaVersion: 1;
readonly operation: 'worker-credential.inspect';
readonly request: {
readonly actionRef: string;
readonly authorityProjectId: string;
readonly approvalRequestId: string;
readonly inspectionId: string;
};
}
export type ClusterWorkerCredentialManagementCommand =
| PlanClusterWorkerCredentialCommand
| ProposeClusterWorkerCredentialCommand
| DecideClusterWorkerCredentialCommand
| InspectClusterWorkerCredentialCommand;
type PlanSummary = ReturnType<typeof planSummary>;
type ApprovalSummary = ReturnType<typeof approvalSummary>;
export type ClusterWorkerCredentialManagementTransportResult =
| Readonly<{
schemaVersion: 1;
operation: 'worker-credential.plan';
status: 'created' | 'existing';
plan: PlanSummary;
}>
| Readonly<{
schemaVersion: 1;
operation: 'worker-credential.propose';
approvalStatus: 'created' | 'existing';
plan: PlanSummary;
approval: ApprovalSummary;
}>
| Readonly<{
schemaVersion: 1;
operation: 'worker-credential.decide';
status: 'decided' | 'existing';
approval: ApprovalSummary;
}>
| Readonly<{
schemaVersion: 1;
operation: 'worker-credential.inspect';
plan: PlanSummary | null;
approval: ApprovalSummary | null;
stale: boolean;
}>;
export interface ClusterWorkerCredentialManagementTransport {
execute(
command: unknown,
authentication: ClusterWorkerCredentialManagementAuthentication,
): Promise<Readonly<ClusterWorkerCredentialManagementTransportResult>>;
}
export interface ClusterWorkerCredentialManagementTransportOptions {
readonly service: ClusterWorkerCredentialManagementService;
readonly now?: () => number;
}
export class ClusterWorkerCredentialManagementTransportConfigurationError extends TypeError {
readonly code = 'CLUSTER_WORKER_CREDENTIAL_TRANSPORT_CONFIGURATION_INVALID';
constructor(message: string) {
super(
`Cluster Worker credential transport configuration is invalid: ${message}`,
);
this.name = 'ClusterWorkerCredentialManagementTransportConfigurationError';
}
}
export class ClusterWorkerCredentialManagementTransportRequestError extends TypeError {
readonly code = 'CLUSTER_WORKER_CREDENTIAL_TRANSPORT_REQUEST_INVALID';
constructor(message: string) {
super(`Cluster Worker credential transport request is invalid: ${message}`);
this.name = 'ClusterWorkerCredentialManagementTransportRequestError';
}
}
export class ClusterWorkerCredentialManagementTransportAuthenticationError extends Error {
readonly code = 'CLUSTER_WORKER_CREDENTIAL_TRANSPORT_AUTHENTICATION_REQUIRED';
constructor() {
super(
'Cluster Worker credential transport requires a strong User principal',
);
this.name = 'ClusterWorkerCredentialManagementTransportAuthenticationError';
}
}
export class ClusterWorkerCredentialManagementTransportUnavailableError extends Error {
readonly code = 'CLUSTER_WORKER_CREDENTIAL_TRANSPORT_UNAVAILABLE';
constructor(readonly cause?: unknown) {
super('Cluster Worker credential transport is unavailable');
this.name = 'ClusterWorkerCredentialManagementTransportUnavailableError';
}
}
function exactObject(
value: unknown,
expectedKeys: readonly string[],
label: string,
): asserts value is Record<string, unknown> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new ClusterWorkerCredentialManagementTransportRequestError(
`${label} must be an object`,
);
}
const actual = Object.keys(value).sort();
const expected = [...expectedKeys].sort();
if (
actual.length !== expected.length ||
actual.some((key, index) => key !== expected[index])
) {
throw new ClusterWorkerCredentialManagementTransportRequestError(
`${label} shape is invalid`,
);
}
}
export function normalizeClusterWorkerCredentialManagementCommand(
value: unknown,
): Readonly<ClusterWorkerCredentialManagementCommand> {
exactObject(value, ['schemaVersion', 'operation', 'request'], 'command');
if (value.schemaVersion !== 1) {
throw new ClusterWorkerCredentialManagementTransportRequestError(
'schemaVersion is invalid',
);
}
switch (value.operation) {
case 'worker-credential.plan':
exactObject(
value.request,
[
'action',
'actionRef',
'authorityProjectId',
'credentialExpiresAtMs',
'credentialId',
'credentialNotBeforeAtMs',
'deliveryId',
'deploymentGeneration',
'deploymentTargetDigest',
'previousCredentialId',
'workerId',
],
'plan request',
);
break;
case 'worker-credential.propose':
exactObject(
value.request,
[
'actionRef',
'authorityProjectId',
'approvalRequestId',
'approvalAuditEventId',
],
'proposal request',
);
break;
case 'worker-credential.decide':
exactObject(
value.request,
[
'actionRef',
'authorityProjectId',
'approvalRequestId',
'expectedVersion',
'decisionId',
'auditEventId',
'decision',
'reasonCode',
],
'decision request',
);
break;
case 'worker-credential.inspect':
exactObject(
value.request,
[
'actionRef',
'authorityProjectId',
'approvalRequestId',
'inspectionId',
],
'inspection request',
);
break;
default:
throw new ClusterWorkerCredentialManagementTransportRequestError(
'operation is not publicly available',
);
}
return Object.freeze(
value as unknown as ClusterWorkerCredentialManagementCommand,
);
}
function planSummary(plan: Readonly<WorkerCredentialManagementPlan>) {
return Object.freeze({
actionRef: plan.actionRef,
authorityProjectId: plan.authorityProjectId,
action: plan.action,
target: Object.freeze({ ...plan.target }),
requestedBy: Object.freeze({ ...plan.requestedBy }),
plannedAtMs: plan.plannedAtMs,
expiresAtMs: plan.expiresAtMs,
previewDigest: plan.previewDigest,
planDigest: plan.planDigest,
});
}
function approvalSummary(approval: Readonly<ApprovalRequestRecord>) {
return Object.freeze({
id: approval.id,
projectId: approval.projectId,
version: approval.version,
state: approval.state,
risk: approval.risk,
decisionMode: approval.decisionMode,
requestedBy: Object.freeze({ ...approval.requestedBy }),
requestedAtMs: approval.requestedAtMs,
expiresAtMs: approval.expiresAtMs,
decision: approval.decision,
decisionReasonCode: approval.decisionReasonCode,
decidedBy: approval.decidedBy
? Object.freeze({ ...approval.decidedBy })
: null,
decidedAtMs: approval.decidedAtMs,
dispatchId: approval.dispatchId,
consumedAtMs: approval.consumedAtMs,
actionType: approval.action.actionType,
actionRef: approval.action.actionRef,
actionDigest: approval.action.actionDigest,
previewDigest: approval.action.previewDigest,
});
}
export function createClusterWorkerCredentialManagementTransport(
options: ClusterWorkerCredentialManagementTransportOptions,
): Readonly<ClusterWorkerCredentialManagementTransport> {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).some((key) => key !== 'service' && key !== 'now') ||
!options.service ||
typeof options.service.plan !== 'function' ||
typeof options.service.propose !== 'function' ||
typeof options.service.decide !== 'function' ||
typeof options.service.inspectAuthorized !== 'function' ||
(options.now !== undefined && typeof options.now !== 'function')
) {
throw new ClusterWorkerCredentialManagementTransportConfigurationError(
'options are invalid',
);
}
const now = options.now ?? Date.now;
return Object.freeze({
async execute(
commandValue: unknown,
authentication: ClusterWorkerCredentialManagementAuthentication,
): Promise<Readonly<ClusterWorkerCredentialManagementTransportResult>> {
const command =
normalizeClusterWorkerCredentialManagementCommand(commandValue);
if (
!authentication ||
typeof authentication !== 'object' ||
Array.isArray(authentication) ||
Object.keys(authentication).some((key) => key !== 'authenticate') ||
typeof authentication.authenticate !== 'function'
) {
throw new ClusterWorkerCredentialManagementTransportConfigurationError(
'authentication authority is invalid',
);
}
let candidate: Readonly<SecurityPrincipal> | null;
try {
candidate = await authentication.authenticate();
} catch (error) {
throw new ClusterWorkerCredentialManagementTransportUnavailableError(
error,
);
}
const observedAtMs = now();
if (!Number.isSafeInteger(observedAtMs) || observedAtMs < 0) {
throw new ClusterWorkerCredentialManagementTransportUnavailableError();
}
let principal: Readonly<SecurityPrincipal>;
try {
principal = normalizeSecurityPrincipal(
candidate as SecurityPrincipal,
observedAtMs,
);
} catch {
throw new ClusterWorkerCredentialManagementTransportAuthenticationError();
}
if (
principal.subject.type !== 'user' ||
!STRONG_CLUSTER_ASSURANCES.has(principal.assurance)
) {
throw new ClusterWorkerCredentialManagementTransportAuthenticationError();
}
switch (command.operation) {
case 'worker-credential.plan': {
const result: Readonly<CreateWorkerCredentialManagementPlanResult> =
await options.service.plan({ ...command.request, principal });
return Object.freeze({
schemaVersion: 1 as const,
operation: command.operation,
status: result.status,
plan: planSummary(result.plan),
});
}
case 'worker-credential.propose': {
const result: Readonly<ProposeClusterWorkerCredentialResult> =
await options.service.propose({ ...command.request, principal });
return Object.freeze({
schemaVersion: 1 as const,
operation: command.operation,
approvalStatus: result.approvalStatus,
plan: planSummary(result.plan),
approval: approvalSummary(result.approvalRequest),
});
}
case 'worker-credential.decide': {
const result = await options.service.decide({
...command.request,
principal,
});
return Object.freeze({
schemaVersion: 1 as const,
operation: command.operation,
status: result.status,
approval: approvalSummary(result.request),
});
}
case 'worker-credential.inspect': {
const result = await options.service.inspectAuthorized({
...command.request,
principal,
});
return Object.freeze({
schemaVersion: 1 as const,
operation: command.operation,
plan: result.plan ? planSummary(result.plan) : null,
approval: result.approvalRequest
? approvalSummary(result.approvalRequest)
: null,
stale: result.stale,
});
}
}
},
});
}
@@ -0,0 +1,268 @@
/** Worker credential administration application boundary. */
import { randomBytes as nodeRandomBytes } from 'node:crypto';
import {
WorkerCredentialMutationConflictError,
normalizeWorkerCredentialId,
normalizeWorkerCredentialMutationId,
type AppendWorkerCredentialResult,
type WorkerCredentialAdministrationOperation,
type WorkerCredentialAdministrationRepository,
} from '@qinglong/runtime-core/worker-credential';
import {
assertWorkerCredentialPepper,
formatWorkerCredentialToken,
workerCredentialSecretDigest,
} from '@qinglong/runtime-core/worker-credential-token';
import {
normalizeSecurityPrincipal,
type SecurityPrincipal,
} from '@qinglong/runtime-core/security';
const MAX_LIFETIME_MS = 2 * 365 * 24 * 60 * 60 * 1000;
const REVOKED_DIGEST = '0'.repeat(64);
const STRONG = new Set(['multi_factor', 'hardware', 'local_console']);
const SAFE_WORKER_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const SAFE_REQUEST_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
export interface WorkerCredentialAdministrationRequest {
readonly mutationId: string;
readonly requestId: string;
readonly expectedCurrentVersion: number;
readonly credentialId: string;
readonly workerId: string;
readonly principal: SecurityPrincipal;
}
export interface ActiveWorkerCredentialAdministrationRequest
extends WorkerCredentialAdministrationRequest {
readonly notBeforeAtMs: number;
readonly expiresAtMs: number;
}
export interface WorkerCredentialAdministrationResult
extends AppendWorkerCredentialResult {
readonly token: string | null;
}
export interface WorkerCredentialAdministrationService {
issue(
request: ActiveWorkerCredentialAdministrationRequest,
): Promise<WorkerCredentialAdministrationResult>;
rotate(
request: ActiveWorkerCredentialAdministrationRequest,
): Promise<WorkerCredentialAdministrationResult>;
revoke(
request: WorkerCredentialAdministrationRequest,
): Promise<WorkerCredentialAdministrationResult>;
}
export interface WorkerCredentialAdministrationOptions {
readonly now?: () => number;
readonly randomBytes?: (size: number) => Buffer;
readonly returnToken?: boolean;
}
function exactRequest(
value: WorkerCredentialAdministrationRequest,
active: boolean,
): void {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new TypeError('Worker credential administration request is invalid');
}
const expected = [
'mutationId',
'requestId',
'expectedCurrentVersion',
'credentialId',
'workerId',
'principal',
...(active ? ['notBeforeAtMs', 'expiresAtMs'] : []),
].sort();
const actual = Object.keys(value).sort();
if (
actual.length !== expected.length ||
actual.some((key, index) => key !== expected[index])
) {
throw new TypeError('Worker credential administration request shape is invalid');
}
normalizeWorkerCredentialMutationId(value.mutationId);
normalizeWorkerCredentialId(value.credentialId);
if (
typeof value.requestId !== 'string' ||
!SAFE_REQUEST_ID.test(value.requestId) ||
typeof value.workerId !== 'string' ||
!SAFE_WORKER_ID.test(value.workerId)
) {
throw new TypeError('Worker credential administration request identity is invalid');
}
}
function principal(value: SecurityPrincipal, nowMs: number) {
const normalized = normalizeSecurityPrincipal(value, nowMs);
if (
!(
(normalized.subject.type === 'user' && STRONG.has(normalized.assurance)) ||
(normalized.subject.type === 'system' && normalized.assurance === 'service')
)
) {
throw new TypeError('Worker credential administration requires a strong principal');
}
return normalized;
}
function sameReplay(
operation: WorkerCredentialAdministrationOperation,
existing: Awaited<ReturnType<WorkerCredentialAdministrationRepository['resolveMutation']>>,
request: WorkerCredentialAdministrationRequest | ActiveWorkerCredentialAdministrationRequest,
): boolean {
if (!existing) return false;
const active = operation === 'revoke'
? null
: request as ActiveWorkerCredentialAdministrationRequest;
return (
existing.mutation.operation === operation &&
existing.mutation.credentialId === request.credentialId &&
existing.mutation.expectedPreviousVersion === request.expectedCurrentVersion &&
existing.credential.workerId === request.workerId &&
existing.credential.state === (operation === 'revoke' ? 'revoked' : 'active') &&
(active === null ||
(existing.credential.notBeforeAtMs === active.notBeforeAtMs &&
existing.credential.expiresAtMs === active.expiresAtMs)) &&
existing.audit.requestId === request.requestId &&
existing.audit.subject?.type === request.principal.subject.type &&
existing.audit.subject.id === request.principal.subject.id
);
}
export function createWorkerCredentialAdministrationService(
repository: WorkerCredentialAdministrationRepository,
pepper: string,
options: WorkerCredentialAdministrationOptions = {},
): WorkerCredentialAdministrationService {
if (
!repository ||
typeof repository.resolveMutation !== 'function' ||
typeof repository.append !== 'function'
) {
throw new TypeError('Worker credential administration repository is invalid');
}
assertWorkerCredentialPepper(pepper);
const now = options.now ?? Date.now;
const randomBytes = options.randomBytes ?? nodeRandomBytes;
const mutate = async (
operation: WorkerCredentialAdministrationOperation,
request: WorkerCredentialAdministrationRequest | ActiveWorkerCredentialAdministrationRequest,
): Promise<WorkerCredentialAdministrationResult> => {
exactRequest(request, operation !== 'revoke');
const nowMs = now();
if (!Number.isSafeInteger(nowMs) || nowMs < 0) {
throw new TypeError('Worker credential administration clock is invalid');
}
const actor = principal(request.principal, nowMs);
if (
(operation === 'issue' && request.expectedCurrentVersion !== 0) ||
(operation !== 'issue' &&
(!Number.isSafeInteger(request.expectedCurrentVersion) ||
request.expectedCurrentVersion < 1))
) {
throw new RangeError('Worker credential administration operation fence is invalid');
}
const existing = await repository.resolveMutation(request.mutationId);
if (existing) {
if (!sameReplay(operation, existing, request)) {
throw new WorkerCredentialMutationConflictError();
}
return Object.freeze({
status: 'existing',
credential: existing.credential,
mutation: existing.mutation,
token: null,
});
}
const active = operation === 'revoke'
? null
: request as ActiveWorkerCredentialAdministrationRequest;
const notBeforeAtMs = active?.notBeforeAtMs ?? nowMs;
const expiresAtMs = active?.expiresAtMs ?? Math.max(nowMs + 1, nowMs + 1_000);
if (
!Number.isSafeInteger(request.expectedCurrentVersion) ||
request.expectedCurrentVersion < 0 ||
!Number.isSafeInteger(notBeforeAtMs) ||
!Number.isSafeInteger(expiresAtMs) ||
notBeforeAtMs < 0 ||
expiresAtMs <= Math.max(nowMs, notBeforeAtMs) ||
expiresAtMs - notBeforeAtMs > MAX_LIFETIME_MS
) {
throw new RangeError('Worker credential administration lifetime or fence is invalid');
}
let secret: Buffer | undefined;
let secretText: string | undefined;
try {
if (operation !== 'revoke') {
secret = randomBytes(32);
if (!Buffer.isBuffer(secret) || secret.byteLength !== 32) {
throw new TypeError('Worker credential administration entropy is invalid');
}
secretText = secret.toString('base64url');
}
const credential = {
credentialId: request.credentialId,
version: request.expectedCurrentVersion + 1,
state: operation === 'revoke' ? 'revoked' as const : 'active' as const,
workerId: request.workerId,
secretDigest: secretText
? workerCredentialSecretDigest(pepper, request.credentialId, secretText)
: REVOKED_DIGEST,
createdAtMs: nowMs,
notBeforeAtMs,
expiresAtMs,
};
const result = await repository.append({
expectedCurrentVersion: request.expectedCurrentVersion,
credential,
mutation: {
mutationId: request.mutationId,
operation,
credentialId: request.credentialId,
credentialVersion: request.expectedCurrentVersion + 1,
expectedPreviousVersion: request.expectedCurrentVersion,
changedBy: actor.subject,
createdAtMs: nowMs,
},
audit: {
eventId: request.mutationId,
requestId: request.requestId,
operationId: `worker_credential.${operation}`,
projectId: null,
subject: actor.subject,
authenticationId: actor.authenticationId,
outcome: 'allowed',
reasons: ['worker_credential_admin'],
fence: null,
occurredAtMs: nowMs,
},
});
return Object.freeze({
...result,
token:
options.returnToken !== false &&
result.status === 'created' && secretText
? formatWorkerCredentialToken(request.credentialId, secretText)
: null,
});
} finally {
secret?.fill(0);
secretText = undefined;
}
};
return Object.freeze({
issue: (request: ActiveWorkerCredentialAdministrationRequest) =>
mutate('issue', request),
rotate: (request: ActiveWorkerCredentialAdministrationRequest) =>
mutate('rotate', request),
revoke: (request: WorkerCredentialAdministrationRequest) =>
mutate('revoke', request),
});
}
@@ -0,0 +1,944 @@
/** Recoverable Worker credential delivery application boundary. */
import {
createHash,
randomBytes as nodeRandomBytes,
} from 'node:crypto';
import {
normalizeWorkerCredentialId,
normalizeWorkerCredentialMutationId,
type AppendWorkerCredentialCommand,
} from '@qinglong/runtime-core/worker-credential';
import {
WorkerCredentialDeliveryConflictError,
WorkerCredentialDeliveryUnavailableError,
MAX_WORKER_CREDENTIAL_STAGE_DISCARD_PAGE_SIZE,
normalizeCommitWorkerCredentialDeliveryCommand,
normalizeRevokePreviousWorkerCredentialDeliveryCommand,
normalizeWorkerCredentialDeliveryIntent,
normalizeWorkerCredentialDeliveryRecoveryPage,
normalizeWorkerCredentialDeliveryRecord,
normalizeWorkerCredentialStageDiscardRecord,
normalizeWorkerCredentialStageDiscardRecoveryPage,
workerCredentialDeliveryTokenDigest,
type ResolvedWorkerCredentialDelivery,
type WorkerCredentialDeliveryAdministrationRepository,
type WorkerCredentialDeliveryIntent,
type WorkerCredentialDeliveryRecoveryPage,
type WorkerCredentialDeliveryRecord,
type WorkerCredentialStageDiscardRecord,
type WorkerCredentialStageDiscardRecoveryPage,
} from '@qinglong/runtime-core/worker-credential-delivery';
import {
formatWorkerCredentialToken,
} from '@qinglong/runtime-core/worker-credential-token';
import {
normalizeSecurityPrincipal,
type SecurityPrincipal,
} from '@qinglong/runtime-core/security';
import {
createWorkerCredentialAdministrationService,
type ActiveWorkerCredentialAdministrationRequest,
} from './workerCredentialAdministration';
const MAX_LIFETIME_MS = 2 * 365 * 24 * 60 * 60 * 1000;
const STRONG = new Set(['multi_factor', 'hardware', 'local_console']);
const SAFE_WORKER_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const SAFE_REQUEST_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const SAFE_GENERATION = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const SHA256 = /^[0-9a-f]{64}$/;
export interface RecoverableWorkerCredentialIssueRequest
extends ActiveWorkerCredentialAdministrationRequest {
readonly previousCredentialId: string | null;
readonly deploymentTargetDigest: string;
readonly deploymentGeneration: string;
}
export interface WorkerCredentialStagedSecretAdapter {
inspect(
deliveryId: string,
): Promise<Readonly<WorkerCredentialDeliveryIntent> | null>;
stage(
delivery: Readonly<WorkerCredentialDeliveryIntent>,
token: Buffer,
): Promise<void>;
publish(
delivery: Readonly<WorkerCredentialDeliveryRecord>,
): Promise<Readonly<{ publicationDigest: string }>>;
discard(delivery: Readonly<WorkerCredentialDeliveryIntent>): Promise<void>;
}
export interface WorkerCredentialStagedSecretPage {
readonly stages: readonly Readonly<WorkerCredentialDeliveryIntent>[];
readonly truncated: boolean;
readonly nextCursor?: string;
}
export interface WorkerCredentialStagedSecretInventoryAdapter
extends WorkerCredentialStagedSecretAdapter {
listStaged(options?: Readonly<{
afterDeliveryId?: string;
limit?: number;
}>): Promise<Readonly<WorkerCredentialStagedSecretPage>>;
}
export interface WorkerCredentialStageCleanupPageResult {
readonly outcomes: readonly Readonly<{
deliveryId: string;
result: 'discarded' | 'already_discarded';
}>[];
readonly truncated: boolean;
readonly nextCursor?: string;
}
export interface WorkerCredentialStageCleanupRecoveryResult
extends WorkerCredentialStageCleanupPageResult {
readonly observedAtMs: number;
}
export interface WorkerCredentialStageCleanupService {
cleanupInventoryPage(options?: Readonly<{
afterDeliveryId?: string;
limit?: number;
}>): Promise<Readonly<WorkerCredentialStageCleanupPageResult>>;
recoverAuthorizedPage(options?: Readonly<{
afterDeliveryId?: string;
limit?: number;
}>): Promise<Readonly<WorkerCredentialStageCleanupRecoveryResult>>;
}
export interface RecoverableWorkerCredentialIssueResult {
readonly status:
| 'published'
| 'existing'
| 'orphaned_stage_discarded';
readonly delivery: Readonly<WorkerCredentialDeliveryRecord> | null;
}
export interface RecoverableWorkerCredentialIssuer {
issue(
request: RecoverableWorkerCredentialIssueRequest,
): Promise<RecoverableWorkerCredentialIssueResult>;
}
export interface RecoverableWorkerCredentialIssuerOptions {
readonly now?: () => number;
readonly randomBytes?: (size: number) => Buffer;
}
export interface WorkerCredentialDeliveryRecoveryResult {
readonly observedAtMs: number;
readonly outcomes: readonly Readonly<{
deliveryId: string;
state: WorkerCredentialDeliveryRecord['state'];
result: 'published' | 'waiting_observation' | 'previous_revoked';
}>[];
readonly truncated: boolean;
readonly nextCursor?: string;
}
export interface WorkerCredentialDeliveryRecoveryService {
recoverPage(options?: Readonly<{
afterDeliveryId?: string;
limit?: number;
}>): Promise<Readonly<WorkerCredentialDeliveryRecoveryResult>>;
}
function exactRequest(value: RecoverableWorkerCredentialIssueRequest): void {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new TypeError('Recoverable Worker credential issue request is invalid');
}
const expected = [
'mutationId',
'requestId',
'expectedCurrentVersion',
'credentialId',
'workerId',
'principal',
'notBeforeAtMs',
'expiresAtMs',
'previousCredentialId',
'deploymentTargetDigest',
'deploymentGeneration',
].sort();
const actual = Object.keys(value).sort();
if (
actual.length !== expected.length ||
actual.some((key, index) => key !== expected[index])
) {
throw new TypeError(
'Recoverable Worker credential issue request shape is invalid',
);
}
}
function validateRequest(
request: RecoverableWorkerCredentialIssueRequest,
nowMs: number,
): void {
exactRequest(request);
normalizeWorkerCredentialMutationId(request.mutationId);
normalizeWorkerCredentialId(request.credentialId);
if (request.previousCredentialId !== null) {
normalizeWorkerCredentialId(request.previousCredentialId);
}
if (
request.expectedCurrentVersion !== 0 ||
request.previousCredentialId === request.credentialId ||
typeof request.workerId !== 'string' ||
!SAFE_WORKER_ID.test(request.workerId) ||
typeof request.requestId !== 'string' ||
!SAFE_REQUEST_ID.test(request.requestId) ||
typeof request.deploymentTargetDigest !== 'string' ||
!SHA256.test(request.deploymentTargetDigest) ||
typeof request.deploymentGeneration !== 'string' ||
!SAFE_GENERATION.test(request.deploymentGeneration)
) {
throw new TypeError('Recoverable Worker credential issue identity is invalid');
}
if (
!Number.isSafeInteger(nowMs) ||
nowMs < 0 ||
!Number.isSafeInteger(request.notBeforeAtMs) ||
request.notBeforeAtMs < 0 ||
!Number.isSafeInteger(request.expiresAtMs) ||
request.expiresAtMs <= Math.max(nowMs, request.notBeforeAtMs) ||
request.expiresAtMs - request.notBeforeAtMs > MAX_LIFETIME_MS
) {
throw new RangeError('Recoverable Worker credential issue lifetime is invalid');
}
const actor = normalizeSecurityPrincipal(request.principal, nowMs);
if (
!(
(actor.subject.type === 'user' && STRONG.has(actor.assurance)) ||
(actor.subject.type === 'system' && actor.assurance === 'service')
)
) {
throw new TypeError(
'Recoverable Worker credential issue requires a strong principal',
);
}
}
function requestForAdministration(
request: RecoverableWorkerCredentialIssueRequest,
): ActiveWorkerCredentialAdministrationRequest {
return {
mutationId: request.mutationId,
requestId: request.requestId,
expectedCurrentVersion: request.expectedCurrentVersion,
credentialId: request.credentialId,
workerId: request.workerId,
principal: request.principal,
notBeforeAtMs: request.notBeforeAtMs,
expiresAtMs: request.expiresAtMs,
};
}
function sameRequest(
resolved: ResolvedWorkerCredentialDelivery,
request: RecoverableWorkerCredentialIssueRequest,
): boolean {
const { delivery, credential, mutation, audit } = resolved;
return (
delivery.deliveryId === request.mutationId &&
delivery.credentialId === request.credentialId &&
delivery.previousCredentialId === request.previousCredentialId &&
delivery.workerId === request.workerId &&
delivery.deploymentTargetDigest === request.deploymentTargetDigest &&
delivery.deploymentGeneration === request.deploymentGeneration &&
credential.notBeforeAtMs === request.notBeforeAtMs &&
credential.expiresAtMs === request.expiresAtMs &&
mutation.operation === 'issue' &&
mutation.expectedPreviousVersion === 0 &&
audit.requestId === request.requestId &&
audit.subject?.type === request.principal.subject.type &&
audit.subject.id === request.principal.subject.id
);
}
function sameStage(
delivery: Readonly<WorkerCredentialDeliveryIntent>,
staged: Readonly<WorkerCredentialDeliveryIntent>,
): boolean {
return (
delivery.deliveryId === staged.deliveryId &&
delivery.workerId === staged.workerId &&
delivery.credentialId === staged.credentialId &&
delivery.credentialVersion === staged.credentialVersion &&
delivery.previousCredentialId === staged.previousCredentialId &&
delivery.secretDigest === staged.secretDigest &&
delivery.tokenDigest === staged.tokenDigest &&
delivery.deploymentTargetDigest === staged.deploymentTargetDigest &&
delivery.deploymentGeneration === staged.deploymentGeneration &&
delivery.stagedAtMs === staged.stagedAtMs
);
}
function mapDeliveryAdapterError(error: unknown): never {
if (error instanceof WorkerCredentialDeliveryConflictError) throw error;
throw new WorkerCredentialDeliveryUnavailableError();
}
function deliveryIntent(
command: Readonly<AppendWorkerCredentialCommand>,
request: RecoverableWorkerCredentialIssueRequest,
digest: string,
): Readonly<WorkerCredentialDeliveryIntent> {
return normalizeWorkerCredentialDeliveryIntent({
deliveryId: command.mutation.mutationId,
workerId: command.credential.workerId,
credentialId: command.credential.credentialId,
credentialVersion: command.credential.version,
previousCredentialId: request.previousCredentialId,
secretDigest: command.credential.secretDigest,
tokenDigest: digest,
deploymentTargetDigest: request.deploymentTargetDigest,
deploymentGeneration: request.deploymentGeneration,
stagedAtMs: command.credential.createdAtMs,
});
}
function committedDelivery(
intent: Readonly<WorkerCredentialDeliveryIntent>,
credentialCommittedAtMs: number,
): Readonly<WorkerCredentialDeliveryRecord> {
return normalizeWorkerCredentialDeliveryRecord({
...intent,
version: 1,
state: 'credential_committed',
credentialCommittedAtMs,
publishedAtMs: null,
publicationDigest: null,
observedAtMs: null,
observedSessionId: null,
observedSessionVersion: null,
previousRevokedAtMs: null,
});
}
export function createRecoverableWorkerCredentialIssuer(
authority: WorkerCredentialDeliveryAdministrationRepository,
deliveryAdapter: WorkerCredentialStagedSecretAdapter,
pepper: string,
options: RecoverableWorkerCredentialIssuerOptions = {},
): RecoverableWorkerCredentialIssuer {
if (
!authority ||
typeof authority.resolveMutation !== 'function' ||
typeof authority.resolveDelivered !== 'function' ||
typeof authority.commitDelivered !== 'function' ||
typeof authority.markPublished !== 'function' ||
typeof authority.authorizeStageDiscard !== 'function' ||
typeof authority.markStageDiscarded !== 'function'
) {
throw new TypeError('Worker credential delivery authority is invalid');
}
if (
!deliveryAdapter ||
typeof deliveryAdapter.inspect !== 'function' ||
typeof deliveryAdapter.stage !== 'function' ||
typeof deliveryAdapter.publish !== 'function' ||
typeof deliveryAdapter.discard !== 'function'
) {
throw new TypeError('Worker credential delivery adapter is invalid');
}
const now = options.now ?? Date.now;
const randomBytes = options.randomBytes ?? nodeRandomBytes;
const publish = async (
delivery: Readonly<WorkerCredentialDeliveryRecord>,
): Promise<Readonly<WorkerCredentialDeliveryRecord>> => {
if (delivery.state !== 'credential_committed') return delivery;
let publication: Readonly<{ publicationDigest: string }>;
try {
publication = await deliveryAdapter.publish(delivery);
} catch (error) {
mapDeliveryAdapterError(error);
}
if (
!publication ||
typeof publication !== 'object' ||
Array.isArray(publication) ||
Object.keys(publication).length !== 1 ||
typeof publication.publicationDigest !== 'string' ||
!SHA256.test(publication.publicationDigest)
) {
throw new WorkerCredentialDeliveryUnavailableError();
}
const publishedAtMs = now();
if (
!Number.isSafeInteger(publishedAtMs) ||
publishedAtMs < delivery.credentialCommittedAtMs
) {
throw new WorkerCredentialDeliveryUnavailableError();
}
try {
return normalizeWorkerCredentialDeliveryRecord(
await authority.markPublished({
deliveryId: delivery.deliveryId,
expectedVersion: delivery.version,
publicationDigest: publication.publicationDigest,
publishedAtMs,
}),
);
} catch (error) {
if (error instanceof WorkerCredentialDeliveryConflictError) throw error;
throw new WorkerCredentialDeliveryUnavailableError();
}
};
return Object.freeze({
async issue(request: RecoverableWorkerCredentialIssueRequest) {
const operationNowMs = now();
validateRequest(request, operationNowMs);
let capturedSecret: Buffer | undefined;
try {
let resolved: ResolvedWorkerCredentialDelivery | null;
try {
resolved = await authority.resolveDelivered(request.mutationId);
} catch {
throw new WorkerCredentialDeliveryUnavailableError();
}
if (resolved) {
if (!sameRequest(resolved, request)) {
throw new WorkerCredentialDeliveryConflictError();
}
let staged: Readonly<WorkerCredentialDeliveryIntent> | null;
try {
const inspected = await deliveryAdapter.inspect(request.mutationId);
staged = inspected
? normalizeWorkerCredentialDeliveryIntent(inspected)
: null;
} catch (error) {
mapDeliveryAdapterError(error);
}
if (!staged || !sameStage(resolved.delivery, staged)) {
throw new WorkerCredentialDeliveryConflictError();
}
const delivery = await publish(resolved.delivery);
return Object.freeze({
status: 'existing' as const,
delivery,
});
}
let rawMutation;
let orphanedStage: Readonly<WorkerCredentialDeliveryIntent> | null;
try {
rawMutation = await authority.resolveMutation(request.mutationId);
const inspected = await deliveryAdapter.inspect(request.mutationId);
orphanedStage = inspected
? normalizeWorkerCredentialDeliveryIntent(inspected)
: null;
} catch (error) {
mapDeliveryAdapterError(error);
}
if (rawMutation) {
throw new WorkerCredentialDeliveryConflictError();
}
if (orphanedStage) {
let authorized: Readonly<WorkerCredentialStageDiscardRecord>;
try {
authorized = normalizeWorkerCredentialStageDiscardRecord(
await authority.authorizeStageDiscard(orphanedStage),
);
} catch (error) {
if (error instanceof WorkerCredentialDeliveryConflictError) throw error;
throw new WorkerCredentialDeliveryUnavailableError();
}
if (!sameStage(authorized, orphanedStage)) {
throw new WorkerCredentialDeliveryConflictError();
}
try {
await deliveryAdapter.discard(orphanedStage);
} catch (error) {
mapDeliveryAdapterError(error);
}
if (authorized.state === 'discard_authorized') {
let completed: Readonly<WorkerCredentialStageDiscardRecord>;
try {
completed = normalizeWorkerCredentialStageDiscardRecord(
await authority.markStageDiscarded({
deliveryId: authorized.deliveryId,
expectedVersion: authorized.version,
}),
);
} catch (error) {
if (error instanceof WorkerCredentialDeliveryConflictError) throw error;
throw new WorkerCredentialDeliveryUnavailableError();
}
if (
completed.state !== 'discarded' ||
completed.authorizedAtMs !== authorized.authorizedAtMs ||
!sameStage(completed, authorized)
) {
throw new WorkerCredentialDeliveryConflictError();
}
}
return Object.freeze({
status: 'orphaned_stage_discarded' as const,
delivery: null,
});
}
const repository = {
resolveMutation: authority.resolveMutation.bind(authority),
async append(command: AppendWorkerCredentialCommand) {
if (!capturedSecret) {
throw new WorkerCredentialDeliveryUnavailableError();
}
let token: Buffer | undefined;
try {
const secretText = capturedSecret.toString('base64url');
token = Buffer.from(
formatWorkerCredentialToken(
command.credential.credentialId,
secretText,
),
'utf8',
);
const intent = deliveryIntent(
command,
request,
workerCredentialDeliveryTokenDigest(token),
);
try {
await deliveryAdapter.stage(intent, token);
} catch (error) {
mapDeliveryAdapterError(error);
}
const delivery = committedDelivery(
intent,
command.credential.createdAtMs,
);
return await authority.commitDelivered(
normalizeCommitWorkerCredentialDeliveryCommand({
credential: command,
delivery,
}),
);
} finally {
token?.fill(0);
capturedSecret.fill(0);
capturedSecret = undefined;
}
},
};
const administration = createWorkerCredentialAdministrationService(
repository,
pepper,
{
now: () => operationNowMs,
randomBytes(size) {
const secret = randomBytes(size);
if (Buffer.isBuffer(secret)) capturedSecret = Buffer.from(secret);
return secret;
},
returnToken: false,
},
);
await administration.issue(requestForAdministration(request));
let committed: ResolvedWorkerCredentialDelivery | null;
try {
committed = await authority.resolveDelivered(request.mutationId);
} catch {
throw new WorkerCredentialDeliveryUnavailableError();
}
if (!committed || !sameRequest(committed, request)) {
throw new WorkerCredentialDeliveryUnavailableError();
}
const delivery = await publish(committed.delivery);
return Object.freeze({ status: 'published' as const, delivery });
} finally {
capturedSecret?.fill(0);
}
},
});
}
const REVOKE_MUTATION_DOMAIN = Buffer.from(
'qinglong/worker-credential-delivery-revoke@v1\0',
'utf8',
);
function revokeMutationId(deliveryId: string): string {
const value = createHash('sha256')
.update(REVOKE_MUTATION_DOMAIN)
.update(deliveryId, 'utf8')
.digest();
value[6] = (value[6]! & 0x0f) | 0x40;
value[8] = (value[8]! & 0x3f) | 0x80;
const hex = value.subarray(0, 16).toString('hex');
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}
export function createWorkerCredentialDeliveryRecoveryService(
authority: WorkerCredentialDeliveryAdministrationRepository,
deliveryAdapter: WorkerCredentialStagedSecretAdapter,
pepper: string,
principal: SecurityPrincipal,
): WorkerCredentialDeliveryRecoveryService {
if (
!authority ||
typeof authority.resolveMutation !== 'function' ||
typeof authority.resolveDelivery !== 'function' ||
typeof authority.markPublished !== 'function' ||
typeof authority.listRecoveryPage !== 'function' ||
typeof authority.revokePreviousDelivered !== 'function'
) {
throw new TypeError('Worker credential recovery authority is invalid');
}
if (
!deliveryAdapter ||
typeof deliveryAdapter.inspect !== 'function' ||
typeof deliveryAdapter.publish !== 'function'
) {
throw new TypeError('Worker credential recovery adapter is invalid');
}
const publish = async (
delivery: Readonly<WorkerCredentialDeliveryRecord>,
operationNowMs: number,
): Promise<Readonly<WorkerCredentialDeliveryRecord>> => {
let staged: Readonly<WorkerCredentialDeliveryIntent> | null;
try {
const inspected = await deliveryAdapter.inspect(delivery.deliveryId);
staged = inspected
? normalizeWorkerCredentialDeliveryIntent(inspected)
: null;
} catch (error) {
mapDeliveryAdapterError(error);
}
if (!staged || !sameStage(delivery, staged)) {
throw new WorkerCredentialDeliveryConflictError();
}
let published: Readonly<{ publicationDigest: string }>;
try {
published = await deliveryAdapter.publish(delivery);
} catch (error) {
mapDeliveryAdapterError(error);
}
if (
!published ||
typeof published !== 'object' ||
Array.isArray(published) ||
Object.keys(published).length !== 1 ||
typeof published.publicationDigest !== 'string' ||
!SHA256.test(published.publicationDigest)
) {
throw new WorkerCredentialDeliveryUnavailableError();
}
if (operationNowMs < delivery.credentialCommittedAtMs) {
throw new WorkerCredentialDeliveryUnavailableError();
}
try {
return normalizeWorkerCredentialDeliveryRecord(
await authority.markPublished({
deliveryId: delivery.deliveryId,
expectedVersion: 1,
publicationDigest: published.publicationDigest,
publishedAtMs: operationNowMs,
}),
);
} catch (error) {
if (error instanceof WorkerCredentialDeliveryConflictError) throw error;
throw new WorkerCredentialDeliveryUnavailableError();
}
};
const revoke = async (
delivery: Readonly<WorkerCredentialDeliveryRecord>,
operationNowMs: number,
): Promise<Readonly<WorkerCredentialDeliveryRecord>> => {
if (delivery.state !== 'observed' || !delivery.previousCredentialId) {
throw new WorkerCredentialDeliveryConflictError();
}
if (operationNowMs < (delivery.observedAtMs ?? 0)) {
throw new WorkerCredentialDeliveryUnavailableError();
}
const repository = {
resolveMutation: authority.resolveMutation.bind(authority),
append(command: AppendWorkerCredentialCommand) {
return authority.revokePreviousDelivered(
normalizeRevokePreviousWorkerCredentialDeliveryCommand({
credential: command,
delivery: normalizeWorkerCredentialDeliveryRecord({
...delivery,
version: 4,
state: 'previous_revoked',
previousRevokedAtMs: command.credential.createdAtMs,
}),
}),
);
},
};
const administration = createWorkerCredentialAdministrationService(
repository,
pepper,
{ now: () => operationNowMs, returnToken: false },
);
await administration.revoke({
mutationId: revokeMutationId(delivery.deliveryId),
requestId: `worker-delivery-revoke:${delivery.deliveryId}`,
expectedCurrentVersion: 1,
credentialId: delivery.previousCredentialId,
workerId: delivery.workerId,
principal,
});
let resolved: Readonly<WorkerCredentialDeliveryRecord> | null;
try {
resolved = await authority.resolveDelivery(delivery.deliveryId);
} catch {
throw new WorkerCredentialDeliveryUnavailableError();
}
if (
!resolved ||
resolved.state !== 'previous_revoked' ||
!sameStage(resolved, delivery)
) {
throw new WorkerCredentialDeliveryConflictError();
}
return resolved;
};
return Object.freeze({
async recoverPage(
requested: Readonly<{
afterDeliveryId?: string;
limit?: number;
}> = {},
) {
let page: Readonly<WorkerCredentialDeliveryRecoveryPage>;
try {
page = normalizeWorkerCredentialDeliveryRecoveryPage(
await authority.listRecoveryPage(requested),
);
} catch (error) {
if (error instanceof WorkerCredentialDeliveryConflictError) throw error;
throw new WorkerCredentialDeliveryUnavailableError();
}
const outcomes = [];
for (const candidate of page.deliveries) {
const delivery = candidate.state === 'credential_committed'
? await publish(candidate, page.observedAtMs)
: candidate.state === 'observed'
? await revoke(candidate, page.observedAtMs)
: candidate;
outcomes.push(Object.freeze({
deliveryId: delivery.deliveryId,
state: delivery.state,
result: candidate.state === 'credential_committed'
? 'published' as const
: candidate.state === 'observed'
? 'previous_revoked' as const
: 'waiting_observation' as const,
}));
}
return Object.freeze({
observedAtMs: page.observedAtMs,
outcomes: Object.freeze(outcomes),
truncated: page.truncated,
...(page.nextCursor === undefined
? {}
: { nextCursor: page.nextCursor }),
});
},
});
}
function normalizeStagedSecretPage(
value: WorkerCredentialStagedSecretPage,
): Readonly<WorkerCredentialStagedSecretPage> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new WorkerCredentialDeliveryUnavailableError();
}
const expected = [
'stages',
'truncated',
...(value.nextCursor === undefined ? [] : ['nextCursor']),
].sort();
const actual = Object.keys(value).sort();
if (
actual.length !== expected.length ||
actual.some((key, index) => key !== expected[index]) ||
!Array.isArray(value.stages) ||
value.stages.length > MAX_WORKER_CREDENTIAL_STAGE_DISCARD_PAGE_SIZE ||
typeof value.truncated !== 'boolean'
) {
throw new WorkerCredentialDeliveryUnavailableError();
}
let stages: Readonly<WorkerCredentialDeliveryIntent>[];
try {
stages = value.stages.map((stage) =>
normalizeWorkerCredentialDeliveryIntent(stage));
} catch {
throw new WorkerCredentialDeliveryUnavailableError();
}
for (let index = 1; index < stages.length; index += 1) {
if (stages[index - 1]!.deliveryId >= stages[index]!.deliveryId) {
throw new WorkerCredentialDeliveryUnavailableError();
}
}
const last = stages[stages.length - 1];
if (
value.truncated !== (value.nextCursor !== undefined) ||
(value.nextCursor !== undefined &&
(!last || value.nextCursor !== last.deliveryId))
) {
throw new WorkerCredentialDeliveryUnavailableError();
}
return Object.freeze({
stages: Object.freeze(stages),
truncated: value.truncated,
...(value.nextCursor === undefined
? {}
: { nextCursor: value.nextCursor }),
});
}
export function createWorkerCredentialStageCleanupService(
authority: WorkerCredentialDeliveryAdministrationRepository,
deliveryAdapter: WorkerCredentialStagedSecretInventoryAdapter,
): WorkerCredentialStageCleanupService {
if (
!authority ||
typeof authority.authorizeStageDiscard !== 'function' ||
typeof authority.markStageDiscarded !== 'function' ||
typeof authority.listStageDiscardRecoveryPage !== 'function'
) {
throw new TypeError('Worker credential stage cleanup authority is invalid');
}
if (
!deliveryAdapter ||
typeof deliveryAdapter.inspect !== 'function' ||
typeof deliveryAdapter.discard !== 'function' ||
typeof deliveryAdapter.listStaged !== 'function'
) {
throw new TypeError('Worker credential stage cleanup adapter is invalid');
}
const discard = async (
record: Readonly<WorkerCredentialStageDiscardRecord>,
staged: Readonly<WorkerCredentialDeliveryIntent> | null,
): Promise<'discarded' | 'already_discarded'> => {
if (!sameStage(record, staged ?? record)) {
throw new WorkerCredentialDeliveryConflictError();
}
if (staged) {
try {
await deliveryAdapter.discard(staged);
} catch (error) {
mapDeliveryAdapterError(error);
}
}
if (record.state === 'discarded') return 'already_discarded';
let completed: Readonly<WorkerCredentialStageDiscardRecord>;
try {
completed = normalizeWorkerCredentialStageDiscardRecord(
await authority.markStageDiscarded({
deliveryId: record.deliveryId,
expectedVersion: record.version,
}),
);
} catch (error) {
if (error instanceof WorkerCredentialDeliveryConflictError) throw error;
throw new WorkerCredentialDeliveryUnavailableError();
}
if (
completed.state !== 'discarded' ||
completed.authorizedAtMs !== record.authorizedAtMs ||
!sameStage(completed, record)
) {
throw new WorkerCredentialDeliveryConflictError();
}
return 'discarded';
};
return Object.freeze({
async cleanupInventoryPage(
options: Readonly<{
afterDeliveryId?: string;
limit?: number;
}> = {},
) {
let page: Readonly<WorkerCredentialStagedSecretPage>;
try {
page = normalizeStagedSecretPage(
await deliveryAdapter.listStaged(options),
);
} catch (error) {
mapDeliveryAdapterError(error);
}
const outcomes = [];
for (const staged of page.stages) {
let authorized: Readonly<WorkerCredentialStageDiscardRecord>;
try {
authorized = normalizeWorkerCredentialStageDiscardRecord(
await authority.authorizeStageDiscard(staged),
);
} catch (error) {
if (error instanceof WorkerCredentialDeliveryConflictError) throw error;
throw new WorkerCredentialDeliveryUnavailableError();
}
if (!sameStage(authorized, staged)) {
throw new WorkerCredentialDeliveryConflictError();
}
outcomes.push(Object.freeze({
deliveryId: staged.deliveryId,
result: await discard(authorized, staged),
}));
}
return Object.freeze({
outcomes: Object.freeze(outcomes),
truncated: page.truncated,
...(page.nextCursor === undefined
? {}
: { nextCursor: page.nextCursor }),
});
},
async recoverAuthorizedPage(
options: Readonly<{
afterDeliveryId?: string;
limit?: number;
}> = {},
) {
let page: Readonly<WorkerCredentialStageDiscardRecoveryPage>;
try {
page = normalizeWorkerCredentialStageDiscardRecoveryPage(
await authority.listStageDiscardRecoveryPage(options),
);
} catch (error) {
if (error instanceof WorkerCredentialDeliveryConflictError) throw error;
throw new WorkerCredentialDeliveryUnavailableError();
}
const outcomes = [];
for (const authorized of page.discards) {
let staged: Readonly<WorkerCredentialDeliveryIntent> | null;
try {
const inspected = await deliveryAdapter.inspect(authorized.deliveryId);
staged = inspected
? normalizeWorkerCredentialDeliveryIntent(inspected)
: null;
} catch (error) {
mapDeliveryAdapterError(error);
}
if (staged && !sameStage(authorized, staged)) {
throw new WorkerCredentialDeliveryConflictError();
}
outcomes.push(Object.freeze({
deliveryId: authorized.deliveryId,
result: await discard(authorized, staged),
}));
}
return Object.freeze({
observedAtMs: page.observedAtMs,
outcomes: Object.freeze(outcomes),
truncated: page.truncated,
...(page.nextCursor === undefined
? {}
: { nextCursor: page.nextCursor }),
});
},
});
}
@@ -0,0 +1,69 @@
#!/usr/bin/env node
/** One-shot Worker credential executor CLI boundary. */
import { runClusterWorkerCredentialExecutorProcess } from './workerCredentialExecutorProcess';
const USAGE = 'Usage: ql3-worker-credential-execute';
function failureFact(error: unknown): Readonly<Record<string, unknown>> {
const candidate = error as { readonly name?: unknown; readonly code?: unknown };
return Object.freeze({
schemaVersion: 1,
component: 'qinglong3-worker-credential-executor',
event: 'execution_failed',
name:
typeof candidate?.name === 'string' && candidate.name.length <= 128
? candidate.name
: 'Error',
...(typeof candidate?.code === 'string' && candidate.code.length <= 128
? { code: candidate.code }
: {}),
});
}
async function run(argv: readonly string[]): Promise<void> {
if (argv.length === 1 && (argv[0] === '--help' || argv[0] === '-h')) {
process.stdout.write(`${USAGE}\n`);
return;
}
if (argv.length !== 0) {
process.stderr.write(
`${JSON.stringify({
code: 'QL3_WORKER_CREDENTIAL_EXECUTOR_CLI_USAGE_INVALID',
message: USAGE,
})}\n`,
);
process.exitCode = 64;
return;
}
try {
const result = await runClusterWorkerCredentialExecutorProcess({
environment: process.env,
});
process.stdout.write(
`${JSON.stringify(
result.status === 'disabled'
? {
schemaVersion: 1,
component: 'qinglong3-worker-credential-executor',
event: 'execution_disabled',
}
: {
schemaVersion: 1,
component: 'qinglong3-worker-credential-executor',
event: 'execution_completed',
actionRef: result.command.actionRef,
dispatchId: result.command.dispatchId,
executionStatus: result.run.execution.status,
deliveryStatus: result.run.result.status,
tokenRequestUsed: result.run.tokenRequest !== null,
},
)}\n`,
);
} catch (error) {
process.stderr.write(`${JSON.stringify(failureFact(error))}\n`);
process.exitCode = 1;
}
}
void run(process.argv.slice(2));
@@ -0,0 +1,640 @@
/** One-shot Worker credential executor process composition boundary. */
import { constants } from 'node:fs';
import { open } from 'node:fs/promises';
import type { OpenPostgresDatabase } from '@qinglong/runtime-core';
import {
createPostgresDatabaseOpener,
isPostgresTlsDnsServername,
loadPostgresCertificateAuthorityFile,
loadPostgresConnectionEnvironment,
type PostgresConnectionOptions,
type PostgresPoolOptions,
} from '@qinglong/cluster-postgres/worker-credential-executor';
import {
absoluteManagementEnvironmentFile,
booleanManagementEnvironmentValue,
boundedManagementEnvironmentValue,
integerManagementEnvironmentValue,
} from '../management-support/managementProcessSupport';
import {
runClusterWorkerCredentialExecution,
type ClusterWorkerCredentialExecutionRun,
type RunClusterWorkerCredentialExecutionOptions,
} from './workerCredentialManagementExecutor';
import type { WorkerCredentialKubernetesDeliveryAdapterOptions } from './workerCredentialKubernetesDelivery';
import {
createWorkerCredentialKubernetesKubeConfigTokenRequestSession,
type WorkerCredentialKubernetesAuthorizationApi,
type WorkerCredentialKubernetesTokenRequestSession,
} from './workerCredentialKubernetesTokenRequest';
const COMMAND_MAX_BYTES = 16 * 1024;
const PEPPER_MAX_BYTES = 256;
const CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
const SAFE_APPLICATION_NAME = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,62}$/;
const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const ACTION_REF = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;
type KubernetesModule = typeof import('@kubernetes/client-node', {
with: { 'resolution-mode': 'import' }
});
export interface ClusterWorkerCredentialExecutorCommand {
readonly schemaVersion: 1;
readonly actionRef: string;
readonly approvalRequestId: string;
readonly consumptionId: string;
readonly dispatchId: string;
readonly auditEventId: string;
}
export type ClusterWorkerCredentialExecutorProcessEnvironment = Readonly<
Record<string, string | undefined>
>;
export type ClusterWorkerCredentialExecutorProcessConfig =
| Readonly<{ enabled: false }>
| Readonly<{
enabled: true;
profile: 'cluster-admin';
commandFile: string;
pepperFile: string;
serviceAccountName: string;
identitySecretName: string;
delivery: WorkerCredentialKubernetesDeliveryAdapterOptions;
database: Readonly<{
connection: PostgresConnectionOptions;
pool: PostgresPoolOptions;
}>;
}>;
export type ClusterWorkerCredentialExecutorProcessResult =
| Readonly<{ status: 'disabled' }>
| Readonly<{
status: 'completed';
command: Readonly<ClusterWorkerCredentialExecutorCommand>;
run: Readonly<ClusterWorkerCredentialExecutionRun>;
}>;
interface KubernetesExecutionAuthority {
readonly session: WorkerCredentialKubernetesTokenRequestSession;
confirmAuthorization(): Promise<void>;
dispose(): void;
}
export interface RunClusterWorkerCredentialExecutorProcessOptions {
readonly environment: ClusterWorkerCredentialExecutorProcessEnvironment;
readonly command?: Readonly<ClusterWorkerCredentialExecutorCommand>;
readonly openDatabase?: OpenPostgresDatabase;
readonly kubernetesAuthority?: KubernetesExecutionAuthority;
readonly createKubernetesAuthority?: (
config: Readonly<ClusterWorkerCredentialExecutorProcessConfig & { enabled: true }>,
) => Promise<KubernetesExecutionAuthority>;
readonly execute?: (
options: RunClusterWorkerCredentialExecutionOptions,
) => Promise<Readonly<ClusterWorkerCredentialExecutionRun>>;
readonly now?: () => number;
}
export class ClusterWorkerCredentialExecutorProcessConfigError extends TypeError {
readonly code = 'QL3_WORKER_CREDENTIAL_EXECUTOR_PROCESS_CONFIG_INVALID';
constructor(message: string) {
super(`Worker credential executor process configuration is invalid: ${message}`);
this.name = 'ClusterWorkerCredentialExecutorProcessConfigError';
}
}
function configFailure(
message: string,
): ClusterWorkerCredentialExecutorProcessConfigError {
return new ClusterWorkerCredentialExecutorProcessConfigError(message);
}
function boundedValue(
environment: ClusterWorkerCredentialExecutorProcessEnvironment,
name: string,
maximumLength: number,
required = false,
): string | undefined {
return boundedManagementEnvironmentValue(
environment,
name,
maximumLength,
configFailure,
required,
);
}
function booleanValue(
environment: ClusterWorkerCredentialExecutorProcessEnvironment,
name: string,
): boolean {
return booleanManagementEnvironmentValue(environment, name, configFailure);
}
function integerValue(
environment: ClusterWorkerCredentialExecutorProcessEnvironment,
name: string,
fallback: number,
minimum: number,
maximum: number,
): number {
return integerManagementEnvironmentValue(
environment,
name,
fallback,
minimum,
maximum,
configFailure,
);
}
function identifier(value: unknown, label: string, pattern = ID): string {
if (typeof value !== 'string' || !pattern.test(value)) {
throw configFailure(`${label} is invalid`);
}
return value;
}
function loadConnection(
environment: ClusterWorkerCredentialExecutorProcessEnvironment,
): Readonly<{
connection: PostgresConnectionOptions;
pool: PostgresPoolOptions;
}> {
let connection: PostgresConnectionOptions;
try {
connection = loadPostgresConnectionEnvironment(environment, {
connectionString: 'QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_URL',
host: 'QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_HOST',
port: 'QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_PORT',
database: 'QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_DATABASE',
user: 'QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_USER',
password: 'QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_PASSWORD',
});
} catch (error) {
throw configFailure(
error instanceof Error
? error.message
: 'PostgreSQL Worker credential executor connection is invalid',
);
}
const mode =
environment.QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_TLS_MODE ??
'verify-full';
if (mode !== 'verify-full' && mode !== 'disable') {
throw configFailure(
'QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_TLS_MODE must be verify-full or disable',
);
}
if (
mode === 'disable' &&
!booleanValue(
environment,
'QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_ALLOW_INSECURE',
)
) {
throw configFailure(
'disabling Worker credential executor PostgreSQL TLS requires explicit opt-in',
);
}
const servername = boundedValue(
environment,
'QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_TLS_SERVERNAME',
253,
);
if (mode === 'verify-full' && !isPostgresTlsDnsServername(servername)) {
throw configFailure(
'QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_TLS_SERVERNAME must be an explicit DNS name',
);
}
const caFile = boundedValue(
environment,
'QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_TLS_CA_FILE',
4_096,
);
if (mode === 'disable' && caFile !== undefined) {
throw configFailure('PostgreSQL CA file cannot be used when TLS is disabled');
}
let ca: string | undefined;
if (caFile !== undefined) {
try {
ca = loadPostgresCertificateAuthorityFile(caFile);
} catch {
throw configFailure('PostgreSQL CA file is invalid');
}
}
const applicationName =
boundedValue(
environment,
'QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_APPLICATION_NAME',
63,
) ?? 'qinglong3-worker-credential-executor';
if (!SAFE_APPLICATION_NAME.test(applicationName)) {
throw configFailure('PostgreSQL application name is invalid');
}
return Object.freeze({
connection: Object.freeze({
...connection,
tls:
mode === 'disable'
? { mode: 'disable' as const }
: {
mode: 'verify-full' as const,
servername: servername!,
...(ca === undefined ? {} : { ca }),
},
}),
pool: Object.freeze({
applicationName,
maxConnections: integerValue(
environment,
'QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_MAX_CONNECTIONS',
1,
1,
2,
),
connectionTimeoutMs: integerValue(
environment,
'QL3_POSTGRES_WORKER_CREDENTIAL_EXECUTOR_CONNECTION_TIMEOUT_MS',
5_000,
100,
60_000,
),
}),
});
}
export function loadClusterWorkerCredentialExecutorProcessConfig(
environment: ClusterWorkerCredentialExecutorProcessEnvironment,
): Readonly<ClusterWorkerCredentialExecutorProcessConfig> {
if (!environment || typeof environment !== 'object') {
throw configFailure('environment is invalid');
}
if (!booleanValue(environment, 'QL3_WORKER_CREDENTIAL_EXECUTOR_ENABLED')) {
return Object.freeze({ enabled: false as const });
}
if (environment.QL3_PROFILE !== 'cluster-admin') {
throw configFailure('QL3_PROFILE must be cluster-admin when executor is enabled');
}
const delivery = Object.freeze({
clusterIdentity: identifier(
boundedValue(
environment,
'QL3_WORKER_CREDENTIAL_EXECUTOR_CLUSTER_IDENTITY',
128,
true,
),
'cluster identity',
),
stageNamespace: identifier(
boundedValue(
environment,
'QL3_WORKER_CREDENTIAL_EXECUTOR_STAGE_NAMESPACE',
63,
true,
),
'stage namespace',
/^[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?$/,
),
namespace: identifier(
boundedValue(
environment,
'QL3_WORKER_CREDENTIAL_EXECUTOR_TARGET_NAMESPACE',
63,
true,
),
'target namespace',
/^[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?$/,
),
targetSecretName: identifier(
boundedValue(
environment,
'QL3_WORKER_CREDENTIAL_EXECUTOR_TARGET_SECRET',
253,
true,
),
'target Secret',
/^[a-z0-9](?:[-a-z0-9.]{0,251}[a-z0-9])?$/,
),
targetDeploymentName: identifier(
boundedValue(
environment,
'QL3_WORKER_CREDENTIAL_EXECUTOR_TARGET_DEPLOYMENT',
253,
true,
),
'target Deployment',
/^[a-z0-9](?:[-a-z0-9.]{0,251}[a-z0-9])?$/,
),
targetDataKey: identifier(
boundedValue(
environment,
'QL3_WORKER_CREDENTIAL_EXECUTOR_TARGET_DATA_KEY',
253,
true,
),
'target data key',
/^[A-Za-z0-9._-]{1,253}$/,
),
});
return Object.freeze({
enabled: true as const,
profile: 'cluster-admin' as const,
commandFile: absoluteManagementEnvironmentFile(
environment,
'QL3_WORKER_CREDENTIAL_EXECUTOR_COMMAND_FILE',
configFailure,
),
pepperFile: absoluteManagementEnvironmentFile(
environment,
'QL3_WORKER_CREDENTIAL_EXECUTOR_PEPPER_FILE',
configFailure,
),
serviceAccountName: identifier(
boundedValue(
environment,
'QL3_WORKER_CREDENTIAL_EXECUTOR_DELIVERY_SERVICE_ACCOUNT',
63,
true,
),
'delivery ServiceAccount',
/^[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?$/,
),
identitySecretName: identifier(
boundedValue(
environment,
'QL3_WORKER_CREDENTIAL_EXECUTOR_IDENTITY_SECRET',
253,
true,
),
'identity Secret',
/^[a-z0-9](?:[-a-z0-9.]{0,251}[a-z0-9])?$/,
),
delivery,
database: loadConnection(environment),
});
}
async function readBoundedFile(
filePath: string,
maximumBytes: number,
privateMaterial: boolean,
): Promise<Buffer> {
const handle = await open(filePath, constants.O_RDONLY);
try {
const before = await handle.stat();
if (
!before.isFile() ||
before.size < 1 ||
before.size > maximumBytes ||
(before.mode & 0o022) !== 0 ||
(privateMaterial && (before.mode & 0o007) !== 0)
) {
throw configFailure('authority file permissions or size are invalid');
}
const bytes = Buffer.alloc(before.size + 1);
let offset = 0;
while (offset < bytes.length) {
const result = await handle.read(
bytes,
offset,
bytes.length - offset,
offset,
);
if (result.bytesRead === 0) break;
offset += result.bytesRead;
}
const after = await handle.stat();
if (
offset !== before.size ||
before.dev !== after.dev ||
before.ino !== after.ino ||
before.size !== after.size ||
before.mtimeMs !== after.mtimeMs ||
before.ctimeMs !== after.ctimeMs
) {
throw configFailure('authority file changed while being read');
}
return bytes.subarray(0, offset);
} finally {
await handle.close().catch(() => undefined);
}
}
function normalizeCommand(
value: unknown,
): Readonly<ClusterWorkerCredentialExecutorCommand> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw configFailure('command must be an object');
}
const command = value as Record<string, unknown>;
const keys = [
'actionRef',
'approvalRequestId',
'auditEventId',
'consumptionId',
'dispatchId',
'schemaVersion',
];
if (
Object.keys(command).sort().join('\0') !== keys.sort().join('\0') ||
command.schemaVersion !== 1
) {
throw configFailure('command shape is invalid');
}
return Object.freeze({
schemaVersion: 1 as const,
actionRef: identifier(command.actionRef, 'actionRef', ACTION_REF),
approvalRequestId: identifier(command.approvalRequestId, 'approvalRequestId'),
consumptionId: identifier(command.consumptionId, 'consumptionId'),
dispatchId: identifier(command.dispatchId, 'dispatchId'),
auditEventId: identifier(command.auditEventId, 'auditEventId'),
});
}
async function loadCommand(
filePath: string,
): Promise<Readonly<ClusterWorkerCredentialExecutorCommand>> {
const bytes = await readBoundedFile(filePath, COMMAND_MAX_BYTES, false);
try {
const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
return normalizeCommand(JSON.parse(text));
} catch (error) {
if (error instanceof ClusterWorkerCredentialExecutorProcessConfigError) {
throw error;
}
throw configFailure('command file is invalid');
}
}
async function loadPepper(filePath: string): Promise<string> {
const bytes = await readBoundedFile(filePath, PEPPER_MAX_BYTES, true);
try {
const value = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
if (
CONTROL_PATTERN.test(value) ||
!/^[A-Za-z0-9_-]{43}$/.test(value) ||
Buffer.from(value, 'base64url').length !== 32 ||
Buffer.from(value, 'base64url').toString('base64url') !== value
) {
throw configFailure('Worker credential pepper is invalid');
}
return value;
} finally {
bytes.fill(0);
}
}
async function createDefaultKubernetesAuthority(
config: Readonly<ClusterWorkerCredentialExecutorProcessConfig & { enabled: true }>,
): Promise<KubernetesExecutionAuthority> {
const kubernetes = (await import('@kubernetes/client-node')) as KubernetesModule;
const issuer = new kubernetes.KubeConfig();
issuer.loadFromCluster();
const authorization = issuer.makeApiClient(
kubernetes.AuthorizationV1Api,
) as unknown as WorkerCredentialKubernetesAuthorizationApi;
const confirmAuthorization = async (): Promise<void> => {
const result = await authorization.createSelfSubjectAccessReview({
body: {
apiVersion: 'authorization.k8s.io/v1',
kind: 'SelfSubjectAccessReview',
spec: {
resourceAttributes: {
namespace: config.delivery.stageNamespace,
verb: 'create',
resource: 'serviceaccounts',
subresource: 'token',
name: config.serviceAccountName,
},
},
},
});
if (result?.status?.allowed !== true || result.status.denied === true) {
throw configFailure('executor Kubernetes authorization is unavailable');
}
};
return Object.freeze({
session: createWorkerCredentialKubernetesKubeConfigTokenRequestSession(
issuer,
kubernetes,
{
serviceAccountName: config.serviceAccountName,
identitySecretName: config.identitySecretName,
delivery: config.delivery,
},
),
confirmAuthorization,
dispose() {
for (const user of issuer.getUsers()) {
const mutable = user as {
token?: string;
certData?: string;
keyData?: string;
};
mutable.token = '';
mutable.certData = '';
mutable.keyData = '';
}
issuer.setCurrentContext('disposed');
},
});
}
export async function runClusterWorkerCredentialExecutorProcess(
options: RunClusterWorkerCredentialExecutorProcessOptions,
): Promise<Readonly<ClusterWorkerCredentialExecutorProcessResult>> {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).some(
(key) =>
![
'environment',
'command',
'openDatabase',
'kubernetesAuthority',
'createKubernetesAuthority',
'execute',
'now',
].includes(key),
) ||
!options.environment ||
typeof options.environment !== 'object' ||
(options.openDatabase !== undefined &&
typeof options.openDatabase !== 'function') ||
(options.createKubernetesAuthority !== undefined &&
typeof options.createKubernetesAuthority !== 'function') ||
(options.execute !== undefined && typeof options.execute !== 'function') ||
(options.now !== undefined && typeof options.now !== 'function')
) {
throw configFailure('options are invalid');
}
const config = loadClusterWorkerCredentialExecutorProcessConfig(
options.environment,
);
if (!config.enabled) return Object.freeze({ status: 'disabled' as const });
const command = normalizeCommand(
options.command ?? (await loadCommand(config.commandFile)),
);
const pepper = await loadPepper(config.pepperFile);
const openDatabase =
options.openDatabase ??
createPostgresDatabaseOpener({
role: 'worker-credential-executor',
connection: config.database.connection,
pool: config.database.pool,
onPoolError() {},
});
const authority =
options.kubernetesAuthority ??
(await (
options.createKubernetesAuthority ?? createDefaultKubernetesAuthority
)(config));
if (
!authority ||
typeof authority !== 'object' ||
!authority.session ||
typeof authority.session.withDelivery !== 'function' ||
typeof authority.confirmAuthorization !== 'function' ||
typeof authority.dispose !== 'function'
) {
throw configFailure('Kubernetes execution authority is invalid');
}
let failure: unknown;
try {
const run = await (options.execute ?? runClusterWorkerCredentialExecution)({
openDatabase,
tokenRequestSession: authority.session,
workerCredentialPepper: pepper,
actionRef: command.actionRef,
approvalRequestId: command.approvalRequestId,
consumptionId: command.consumptionId,
dispatchId: command.dispatchId,
auditEventId: command.auditEventId,
confirmAuthorization: authority.confirmAuthorization,
...(options.now === undefined ? {} : { now: options.now }),
});
return Object.freeze({ status: 'completed' as const, command, run });
} catch (error) {
failure = error;
throw error;
} finally {
try {
authority.dispose();
} catch (disposeError) {
if (failure !== undefined) {
throw new AggregateError(
[failure, disposeError],
'Worker credential executor failed and Kubernetes authority did not dispose',
);
}
throw disposeError;
}
}
}
@@ -0,0 +1,794 @@
/** POSIX file-backed Worker credential delivery adapter boundary. */
import { createHash, randomUUID } from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import {
WorkerCredentialDeliveryConflictError,
WorkerCredentialDeliveryUnavailableError,
normalizeWorkerCredentialDeliveryIntent,
normalizeWorkerCredentialDeliveryRecord,
workerCredentialDeliveryTokenDigest,
type WorkerCredentialDeliveryIntent,
type WorkerCredentialDeliveryRecord,
} from '@qinglong/runtime-core/worker-credential-delivery';
import type {
WorkerCredentialStagedSecretInventoryAdapter,
WorkerCredentialStagedSecretPage,
} from './workerCredentialDelivery';
const MAX_PATH_BYTES = 4096;
const MAX_STAGE_BYTES = 8192;
const MAX_STAGE_HEADER_BYTES = 4096;
const MAX_TOKEN_BYTES = 256;
export const MAX_WORKER_CREDENTIAL_FILE_STAGES = 128;
export const MAX_WORKER_CREDENTIAL_FILE_STAGE_PAGE_SIZE = 64;
const STAGE_MAGIC = Buffer.from(
'qinglong/worker-credential-file-stage@v1\n',
'ascii',
);
const TARGET_DIGEST_DOMAIN = Buffer.from(
'qinglong/worker-credential-file-target@v1\0',
'utf8',
);
const PUBLICATION_DIGEST_DOMAIN = Buffer.from(
'qinglong/worker-credential-file-publication@v1\0',
'utf8',
);
const UUID_V4 =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
const STAGE_NAME =
/^([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\.stage$/;
const STAGE_TEMP_NAME =
/^\.([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\.[0-9a-f-]{36}\.tmp$/;
const TOKEN =
/^ql3w_([A-Za-z0-9][A-Za-z0-9._:-]{0,63})_([A-Za-z0-9_-]{43})$/;
const TARGET_LOCK_NAME = '.ql3-worker-credential-delivery.lock';
export interface WorkerCredentialFileDeliveryAdapterOptions {
/** Dedicated private 0700 directory containing bounded durable stages. */
readonly stageDirectory: string;
/** Atomically replaceable 0600 ql3w token file read by worker-runtime. */
readonly targetTokenFile: string;
}
export type WorkerCredentialFileStagePage = WorkerCredentialStagedSecretPage;
interface ParsedToken {
readonly credentialId: string;
readonly tokenDigest: string;
}
interface StagedSecret {
readonly intent: Readonly<WorkerCredentialDeliveryIntent>;
readonly token: Buffer;
}
interface OwnedTargetLock {
readonly device: bigint;
readonly inode: bigint;
}
function isMissing(error: unknown): boolean {
return Boolean(
error &&
typeof error === 'object' &&
'code' in error &&
error.code === 'ENOENT',
);
}
function isExists(error: unknown): boolean {
return Boolean(
error &&
typeof error === 'object' &&
'code' in error &&
error.code === 'EEXIST',
);
}
function boundedAbsolutePath(value: string, name: string): string {
if (
typeof value !== 'string' ||
!path.isAbsolute(value) ||
path.parse(value).root === value ||
path.normalize(value) !== value ||
value.includes('\0') ||
Buffer.byteLength(value, 'utf8') > MAX_PATH_BYTES
) {
throw new TypeError(`${name} must be a bounded canonical absolute path`);
}
return value;
}
class PrivateDirectoryAuthority {
readonly directory: string;
readonly uid: number;
readonly device: bigint;
readonly inode: bigint;
constructor(directory: string, name: string) {
this.directory = boundedAbsolutePath(directory, name);
if (typeof process.getuid !== 'function') {
throw new TypeError(`${name} requires a POSIX process identity`);
}
this.uid = process.getuid();
const stat = fs.lstatSync(this.directory, { bigint: true });
if (
!stat.isDirectory() ||
stat.isSymbolicLink() ||
Number(stat.uid) !== this.uid ||
(Number(stat.mode) & 0o777) !== 0o700
) {
throw new TypeError(`${name} must be a private owned real directory`);
}
this.device = stat.dev;
this.inode = stat.ino;
}
verify(): void {
const stat = fs.lstatSync(this.directory, { bigint: true });
if (
!stat.isDirectory() ||
stat.isSymbolicLink() ||
Number(stat.uid) !== this.uid ||
(Number(stat.mode) & 0o777) !== 0o700 ||
stat.dev !== this.device ||
stat.ino !== this.inode
) {
throw new WorkerCredentialDeliveryUnavailableError();
}
}
sync(): void {
this.verify();
const descriptor = fs.openSync(this.directory, 'r');
try {
fs.fsyncSync(descriptor);
} finally {
fs.closeSync(descriptor);
}
}
}
function readPrivateFile(
authority: PrivateDirectoryAuthority,
filePath: string,
maximumBytes: number,
): Buffer {
authority.verify();
const before = fs.lstatSync(filePath, { bigint: true });
if (
!before.isFile() ||
before.isSymbolicLink() ||
Number(before.uid) !== authority.uid ||
(Number(before.mode) & 0o777) !== 0o600 ||
before.size < 1n ||
before.size > BigInt(maximumBytes)
) {
throw new WorkerCredentialDeliveryUnavailableError();
}
const descriptor = fs.openSync(
filePath,
fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0),
);
try {
const opened = fs.fstatSync(descriptor, { bigint: true });
if (
!opened.isFile() ||
opened.dev !== before.dev ||
opened.ino !== before.ino ||
opened.size !== before.size
) {
throw new WorkerCredentialDeliveryUnavailableError();
}
const bytes = fs.readFileSync(descriptor);
if (bytes.byteLength < 1 || bytes.byteLength > maximumBytes) {
bytes.fill(0);
throw new WorkerCredentialDeliveryUnavailableError();
}
return bytes;
} finally {
fs.closeSync(descriptor);
}
}
function parseToken(bytes: Buffer): ParsedToken {
let visible = bytes;
if (visible[visible.byteLength - 1] === 0x0a) {
visible = visible.subarray(0, -1);
}
if (
visible.byteLength < 1 ||
visible.byteLength > MAX_TOKEN_BYTES ||
visible.includes(0x0a) ||
visible.some((byte) => byte > 0x7f)
) {
throw new WorkerCredentialDeliveryUnavailableError();
}
const match = TOKEN.exec(visible.toString('ascii'));
if (!match) throw new WorkerCredentialDeliveryUnavailableError();
return Object.freeze({
credentialId: match[1]!,
tokenDigest: workerCredentialDeliveryTokenDigest(visible),
});
}
function sameIntent(
left: Readonly<WorkerCredentialDeliveryIntent>,
right: Readonly<WorkerCredentialDeliveryIntent>,
): boolean {
return (
left.deliveryId === right.deliveryId &&
left.workerId === right.workerId &&
left.credentialId === right.credentialId &&
left.credentialVersion === right.credentialVersion &&
left.previousCredentialId === right.previousCredentialId &&
left.secretDigest === right.secretDigest &&
left.tokenDigest === right.tokenDigest &&
left.deploymentTargetDigest === right.deploymentTargetDigest &&
left.deploymentGeneration === right.deploymentGeneration &&
left.stagedAtMs === right.stagedAtMs
);
}
function recordMatchesIntent(
record: Readonly<WorkerCredentialDeliveryRecord>,
intent: Readonly<WorkerCredentialDeliveryIntent>,
): boolean {
return sameIntent(record, intent);
}
function preserveDomainError(error: unknown): never {
if (
error instanceof WorkerCredentialDeliveryConflictError ||
error instanceof WorkerCredentialDeliveryUnavailableError
) {
throw error;
}
throw new WorkerCredentialDeliveryUnavailableError();
}
/**
* Concrete short-lived POSIX adapter for Docker bind mounts, systemd services,
* and controlled shared volumes. It owns no timer, socket, database or cache.
*/
export class WorkerCredentialFileDeliveryAdapter
implements WorkerCredentialStagedSecretInventoryAdapter {
readonly deploymentTargetDigest: string;
private readonly stages: PrivateDirectoryAuthority;
private readonly targetParent: PrivateDirectoryAuthority;
private readonly targetTokenFile: string;
private readonly targetLockFile: string;
constructor(options: WorkerCredentialFileDeliveryAdapterOptions) {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).length !== 2 ||
!Object.prototype.hasOwnProperty.call(options, 'stageDirectory') ||
!Object.prototype.hasOwnProperty.call(options, 'targetTokenFile')
) {
throw new TypeError('Worker credential file delivery options are invalid');
}
this.stages = new PrivateDirectoryAuthority(
options.stageDirectory,
'Worker credential stage directory',
);
this.targetTokenFile = boundedAbsolutePath(
options.targetTokenFile,
'Worker credential target token file',
);
const targetName = path.basename(this.targetTokenFile);
if (targetName === '.' || targetName === '..' || targetName.startsWith('.ql3w-')) {
throw new TypeError('Worker credential target token name is invalid');
}
this.targetParent = new PrivateDirectoryAuthority(
path.dirname(this.targetTokenFile),
'Worker credential target directory',
);
if (
this.stages.device === this.targetParent.device &&
this.stages.inode === this.targetParent.inode
) {
throw new TypeError('Worker credential stage and target directories must differ');
}
this.targetLockFile = path.join(
this.targetParent.directory,
TARGET_LOCK_NAME,
);
this.deploymentTargetDigest = createHash('sha256')
.update(TARGET_DIGEST_DOMAIN)
.update(this.targetTokenFile, 'utf8')
.update('\0', 'utf8')
.update(String(this.targetParent.uid), 'utf8')
.update('\0', 'utf8')
.update(this.targetParent.device.toString(), 'utf8')
.update('\0', 'utf8')
.update(this.targetParent.inode.toString(), 'utf8')
.digest('hex');
}
private stagePath(deliveryId: string): string {
if (!UUID_V4.test(deliveryId)) {
throw new WorkerCredentialDeliveryConflictError();
}
return path.join(this.stages.directory, `${deliveryId}.stage`);
}
private verifyStageCapacity(): void {
this.stages.verify();
const directory = fs.opendirSync(this.stages.directory);
let count = 0;
try {
for (;;) {
const entry = directory.readSync();
if (!entry) break;
count += 1;
if (
count >= MAX_WORKER_CREDENTIAL_FILE_STAGES ||
!STAGE_NAME.test(entry.name)
) {
throw new WorkerCredentialDeliveryUnavailableError();
}
}
} finally {
directory.closeSync();
}
}
private stableStageNames(): readonly string[] {
this.stages.verify();
const directory = fs.opendirSync(this.stages.directory);
const names: string[] = [];
let entries = 0;
try {
for (;;) {
const entry = directory.readSync();
if (!entry) break;
entries += 1;
if (entries > MAX_WORKER_CREDENTIAL_FILE_STAGES) {
throw new WorkerCredentialDeliveryUnavailableError();
}
const match = STAGE_NAME.exec(entry.name);
if (match) {
names.push(match[1]!);
continue;
}
if (STAGE_TEMP_NAME.test(entry.name)) {
throw new WorkerCredentialDeliveryUnavailableError();
}
throw new WorkerCredentialDeliveryUnavailableError();
}
} finally {
directory.closeSync();
}
return Object.freeze(names.sort());
}
private normalizeIntent(
value: WorkerCredentialDeliveryIntent,
): Readonly<WorkerCredentialDeliveryIntent> {
const intent = normalizeWorkerCredentialDeliveryIntent(value);
if (intent.deploymentTargetDigest !== this.deploymentTargetDigest) {
throw new WorkerCredentialDeliveryConflictError();
}
return intent;
}
private readStage(deliveryId: string): StagedSecret {
const material = readPrivateFile(
this.stages,
this.stagePath(deliveryId),
MAX_STAGE_BYTES,
);
let token: Buffer | undefined;
try {
if (!material.subarray(0, STAGE_MAGIC.byteLength).equals(STAGE_MAGIC)) {
throw new WorkerCredentialDeliveryUnavailableError();
}
const headerEnd = material.indexOf(0x0a, STAGE_MAGIC.byteLength);
const headerBytes = headerEnd - STAGE_MAGIC.byteLength;
if (
headerEnd < STAGE_MAGIC.byteLength ||
headerBytes < 2 ||
headerBytes > MAX_STAGE_HEADER_BYTES ||
headerEnd + 1 >= material.byteLength
) {
throw new WorkerCredentialDeliveryUnavailableError();
}
const intent = this.normalizeIntent(
JSON.parse(
material
.subarray(STAGE_MAGIC.byteLength, headerEnd)
.toString('utf8'),
),
);
if (intent.deliveryId !== deliveryId) {
throw new WorkerCredentialDeliveryConflictError();
}
token = Buffer.from(material.subarray(headerEnd + 1));
const parsed = parseToken(token);
if (
parsed.credentialId !== intent.credentialId ||
parsed.tokenDigest !== intent.tokenDigest
) {
throw new WorkerCredentialDeliveryConflictError();
}
const result = Object.freeze({ intent, token });
token = undefined;
return result;
} finally {
token?.fill(0);
material.fill(0);
}
}
private optionalStage(deliveryId: string): StagedSecret | null {
try {
return this.readStage(deliveryId);
} catch (error) {
if (isMissing(error)) return null;
throw error;
}
}
private optionalTarget(): ParsedToken | null {
let material: Buffer | undefined;
try {
material = readPrivateFile(
this.targetParent,
this.targetTokenFile,
MAX_TOKEN_BYTES,
);
return parseToken(material);
} catch (error) {
if (isMissing(error)) return null;
throw error;
} finally {
material?.fill(0);
}
}
private publicationDigest(
delivery: Readonly<WorkerCredentialDeliveryRecord>,
): string {
return createHash('sha256')
.update(PUBLICATION_DIGEST_DOMAIN)
.update(JSON.stringify({
deliveryId: delivery.deliveryId,
workerId: delivery.workerId,
credentialId: delivery.credentialId,
credentialVersion: delivery.credentialVersion,
previousCredentialId: delivery.previousCredentialId,
tokenDigest: delivery.tokenDigest,
deploymentTargetDigest: delivery.deploymentTargetDigest,
deploymentGeneration: delivery.deploymentGeneration,
}), 'utf8')
.digest('hex');
}
private assertTargetFence(
delivery: Readonly<WorkerCredentialDeliveryRecord>,
target: ParsedToken | null,
): 'published' | 'replace' {
if (
target?.credentialId === delivery.credentialId &&
target.tokenDigest === delivery.tokenDigest
) {
return 'published';
}
if (
target?.credentialId === delivery.credentialId ||
(delivery.previousCredentialId === null && target !== null) ||
(delivery.previousCredentialId !== null &&
target?.credentialId !== delivery.previousCredentialId)
) {
throw new WorkerCredentialDeliveryConflictError();
}
return 'replace';
}
private acquireTargetLock(deliveryId: string): OwnedTargetLock {
this.targetParent.verify();
let descriptor: number | undefined;
try {
descriptor = fs.openSync(
this.targetLockFile,
fs.constants.O_WRONLY |
fs.constants.O_CREAT |
fs.constants.O_EXCL |
(fs.constants.O_NOFOLLOW ?? 0),
0o600,
);
fs.writeFileSync(descriptor, `${JSON.stringify({ deliveryId })}\n`, 'utf8');
fs.fsyncSync(descriptor);
const stat = fs.fstatSync(descriptor, { bigint: true });
fs.closeSync(descriptor);
descriptor = undefined;
this.targetParent.sync();
return Object.freeze({ device: stat.dev, inode: stat.ino });
} catch (error) {
if (descriptor !== undefined) fs.closeSync(descriptor);
if (isExists(error)) throw new WorkerCredentialDeliveryUnavailableError();
preserveDomainError(error);
}
}
private releaseTargetLock(lock: OwnedTargetLock): void {
try {
const stat = fs.lstatSync(this.targetLockFile, { bigint: true });
if (
!stat.isFile() ||
stat.isSymbolicLink() ||
Number(stat.uid) !== this.targetParent.uid ||
(Number(stat.mode) & 0o777) !== 0o600 ||
stat.dev !== lock.device ||
stat.ino !== lock.inode
) {
return;
}
fs.unlinkSync(this.targetLockFile);
this.targetParent.sync();
} catch {
// A stale lock fails future rotations closed and requires explicit repair.
}
}
async inspect(
deliveryId: string,
): Promise<Readonly<WorkerCredentialDeliveryIntent> | null> {
let staged: StagedSecret | null = null;
try {
staged = this.optionalStage(deliveryId);
return staged?.intent ?? null;
} catch (error) {
return preserveDomainError(error);
} finally {
staged?.token.fill(0);
}
}
async listStaged(
options: Readonly<{ afterDeliveryId?: string; limit?: number }> = {},
): Promise<Readonly<WorkerCredentialFileStagePage>> {
try {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).some(
(key) => key !== 'afterDeliveryId' && key !== 'limit',
)
) {
throw new WorkerCredentialDeliveryConflictError();
}
const limit = options.limit ?? 16;
if (
!Number.isInteger(limit) ||
limit < 1 ||
limit > MAX_WORKER_CREDENTIAL_FILE_STAGE_PAGE_SIZE ||
(options.afterDeliveryId !== undefined &&
!UUID_V4.test(options.afterDeliveryId))
) {
throw new WorkerCredentialDeliveryConflictError();
}
const names = this.stableStageNames().filter(
(name) =>
options.afterDeliveryId === undefined ||
name > options.afterDeliveryId,
);
const selected = names.slice(0, limit + 1);
const stages: Readonly<WorkerCredentialDeliveryIntent>[] = [];
for (const deliveryId of selected.slice(0, limit)) {
const staged = this.readStage(deliveryId);
try {
stages.push(staged.intent);
} finally {
staged.token.fill(0);
}
}
const truncated = selected.length > limit;
return Object.freeze({
stages: Object.freeze(stages),
truncated,
...(truncated
? { nextCursor: stages[stages.length - 1]!.deliveryId }
: {}),
});
} catch (error) {
return preserveDomainError(error);
}
}
async stage(
value: Readonly<WorkerCredentialDeliveryIntent>,
token: Buffer,
): Promise<void> {
let serialized: Buffer | undefined;
let descriptor: number | undefined;
const intent = this.normalizeIntent(value);
const parsed = Buffer.isBuffer(token) ? parseToken(token) : null;
if (
!parsed ||
parsed.credentialId !== intent.credentialId ||
parsed.tokenDigest !== intent.tokenDigest
) {
throw new WorkerCredentialDeliveryConflictError();
}
const targetPath = this.stagePath(intent.deliveryId);
const temporaryPath = path.join(
this.stages.directory,
`.${intent.deliveryId}.${randomUUID()}.tmp`,
);
try {
const existing = this.optionalStage(intent.deliveryId);
if (existing) {
try {
if (!sameIntent(existing.intent, intent)) {
throw new WorkerCredentialDeliveryConflictError();
}
return;
} finally {
existing.token.fill(0);
}
}
this.verifyStageCapacity();
const header = Buffer.from(JSON.stringify(intent), 'utf8');
if (header.byteLength > MAX_STAGE_HEADER_BYTES) {
throw new WorkerCredentialDeliveryUnavailableError();
}
serialized = Buffer.concat([
STAGE_MAGIC,
header,
Buffer.from('\n', 'ascii'),
token,
]);
header.fill(0);
if (serialized.byteLength > MAX_STAGE_BYTES) {
throw new WorkerCredentialDeliveryUnavailableError();
}
descriptor = fs.openSync(
temporaryPath,
fs.constants.O_WRONLY |
fs.constants.O_CREAT |
fs.constants.O_EXCL |
(fs.constants.O_NOFOLLOW ?? 0),
0o600,
);
fs.writeFileSync(descriptor, serialized);
fs.fsyncSync(descriptor);
fs.closeSync(descriptor);
descriptor = undefined;
try {
fs.linkSync(temporaryPath, targetPath);
this.stages.sync();
} catch (error) {
if (!isExists(error)) throw error;
}
try {
fs.unlinkSync(temporaryPath);
this.stages.sync();
} catch (error) {
if (!isMissing(error)) throw error;
}
const winner = this.readStage(intent.deliveryId);
try {
if (!sameIntent(winner.intent, intent)) {
throw new WorkerCredentialDeliveryConflictError();
}
} finally {
winner.token.fill(0);
}
} catch (error) {
return preserveDomainError(error);
} finally {
serialized?.fill(0);
if (descriptor !== undefined) fs.closeSync(descriptor);
try {
fs.unlinkSync(temporaryPath);
this.stages.sync();
} catch {
// The no-replace stage, if published, remains authoritative.
}
}
}
async publish(
value: Readonly<WorkerCredentialDeliveryRecord>,
): Promise<Readonly<{ publicationDigest: string }>> {
const delivery = normalizeWorkerCredentialDeliveryRecord(value);
if (
delivery.state !== 'credential_committed' ||
delivery.version !== 1 ||
delivery.deploymentTargetDigest !== this.deploymentTargetDigest
) {
throw new WorkerCredentialDeliveryConflictError();
}
let staged: StagedSecret | null = null;
let temporaryPath: string | undefined;
let descriptor: number | undefined;
let targetLock: OwnedTargetLock | undefined;
try {
staged = this.optionalStage(delivery.deliveryId);
if (!staged || !recordMatchesIntent(delivery, staged.intent)) {
throw new WorkerCredentialDeliveryConflictError();
}
if (this.assertTargetFence(delivery, this.optionalTarget()) === 'published') {
return Object.freeze({
publicationDigest: this.publicationDigest(delivery),
});
}
targetLock = this.acquireTargetLock(delivery.deliveryId);
if (this.assertTargetFence(delivery, this.optionalTarget()) === 'published') {
return Object.freeze({
publicationDigest: this.publicationDigest(delivery),
});
}
temporaryPath = path.join(
this.targetParent.directory,
`.ql3w-${delivery.deliveryId}-${randomUUID()}.tmp`,
);
descriptor = fs.openSync(
temporaryPath,
fs.constants.O_WRONLY |
fs.constants.O_CREAT |
fs.constants.O_EXCL |
(fs.constants.O_NOFOLLOW ?? 0),
0o600,
);
fs.writeFileSync(descriptor, staged.token);
fs.fsyncSync(descriptor);
fs.closeSync(descriptor);
descriptor = undefined;
fs.renameSync(temporaryPath, this.targetTokenFile);
temporaryPath = undefined;
this.targetParent.sync();
if (this.assertTargetFence(delivery, this.optionalTarget()) !== 'published') {
throw new WorkerCredentialDeliveryUnavailableError();
}
return Object.freeze({
publicationDigest: this.publicationDigest(delivery),
});
} catch (error) {
return preserveDomainError(error);
} finally {
staged?.token.fill(0);
if (descriptor !== undefined) fs.closeSync(descriptor);
if (temporaryPath) {
try {
fs.unlinkSync(temporaryPath);
this.targetParent.sync();
} catch {
// The target was never published from this temporary path.
}
}
if (targetLock) this.releaseTargetLock(targetLock);
}
}
async discard(value: Readonly<WorkerCredentialDeliveryIntent>): Promise<void> {
const intent = this.normalizeIntent(value);
let staged: StagedSecret | null = null;
try {
staged = this.optionalStage(intent.deliveryId);
if (!staged) return;
if (!sameIntent(staged.intent, intent)) {
throw new WorkerCredentialDeliveryConflictError();
}
const target = this.optionalTarget();
if (
target?.credentialId === intent.credentialId &&
target.tokenDigest === intent.tokenDigest
) {
throw new WorkerCredentialDeliveryConflictError();
}
fs.unlinkSync(this.stagePath(intent.deliveryId));
this.stages.sync();
} catch (error) {
preserveDomainError(error);
} finally {
staged?.token.fill(0);
}
}
}
@@ -0,0 +1,549 @@
/** Short-lived Kubernetes TokenRequest delivery session boundary. */
import {
WorkerCredentialKubernetesDeliveryAdapter,
type WorkerCredentialKubernetesDeliveryAdapterOptions,
type WorkerCredentialKubernetesDeploymentApi,
type WorkerCredentialKubernetesSecretApi,
} from './workerCredentialKubernetesDelivery';
export const WORKER_CREDENTIAL_KUBERNETES_TOKEN_REQUEST_SECONDS = 600;
const MIN_USEFUL_TOKEN_SECONDS = 30;
const MAX_TOKEN_BYTES = 16 * 1024;
const DNS_LABEL = /^[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?$/;
const DNS_SUBDOMAIN =
/^[a-z0-9](?:[-a-z0-9.]{0,251}[a-z0-9])?$/;
const SAFE_JWT_ALGORITHM = /^[A-Za-z0-9_-]{2,32}$/;
const JWT = /^([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]+)$/;
type JsonObject = Record<string, unknown>;
type KubernetesModule = typeof import('@kubernetes/client-node', {
with: { 'resolution-mode': 'import' }
});
type KubernetesConfig = InstanceType<KubernetesModule['KubeConfig']>;
interface TokenRequestResponse {
apiVersion?: string;
kind?: string;
status?: {
token?: string;
expirationTimestamp?: Date | string;
};
}
interface AccessReviewAttributes {
readonly namespace?: string;
readonly verb: string;
readonly group?: string;
readonly resource: string;
readonly subresource?: string;
readonly name?: string;
}
export interface WorkerCredentialKubernetesTokenRequestApi {
createNamespacedServiceAccountToken(request: Readonly<{
name: string;
namespace: string;
body: Readonly<{
apiVersion: 'authentication.k8s.io/v1';
kind: 'TokenRequest';
spec: Readonly<{ expirationSeconds: 600 }>;
}>;
}>): Promise<TokenRequestResponse>;
}
export interface WorkerCredentialKubernetesAuthorizationApi {
createSelfSubjectAccessReview(request: Readonly<{
body: Readonly<{
apiVersion: 'authorization.k8s.io/v1';
kind: 'SelfSubjectAccessReview';
spec: Readonly<{
resourceAttributes: AccessReviewAttributes;
}>;
}>;
}>): Promise<Readonly<{
status?: Readonly<{
allowed?: boolean;
denied?: boolean;
reason?: string;
}>;
}>>;
}
export interface WorkerCredentialKubernetesRestrictedClients {
readonly secrets: WorkerCredentialKubernetesSecretApi;
readonly deployments: WorkerCredentialKubernetesDeploymentApi;
readonly authorization: WorkerCredentialKubernetesAuthorizationApi;
dispose(): void | Promise<void>;
}
export interface WorkerCredentialKubernetesTokenRequestSessionOptions {
readonly serviceAccountName: string;
readonly identitySecretName: string;
readonly delivery: WorkerCredentialKubernetesDeliveryAdapterOptions;
readonly now?: () => number;
}
export interface WorkerCredentialKubernetesTokenRequestEvidence {
readonly tokenLifetimeSeconds: number;
readonly issuerAllowedChecks: number;
readonly issuerDeniedChecks: number;
readonly allowedChecks: number;
readonly deniedChecks: number;
}
export interface WorkerCredentialKubernetesTokenRequestContext {
readonly delivery: WorkerCredentialKubernetesDeliveryAdapter;
readonly evidence: WorkerCredentialKubernetesTokenRequestEvidence;
}
export interface WorkerCredentialKubernetesTokenRequestSession {
withDelivery<T>(
operation: (
context: Readonly<WorkerCredentialKubernetesTokenRequestContext>,
) => Promise<T>,
): Promise<T>;
}
export class WorkerCredentialKubernetesTokenRequestUnavailableError
extends Error {
readonly code = 'QL3_WORKER_CREDENTIAL_KUBERNETES_TOKEN_REQUEST_UNAVAILABLE';
constructor() {
super('Worker credential Kubernetes TokenRequest session is unavailable');
this.name = 'WorkerCredentialKubernetesTokenRequestUnavailableError';
}
}
const VALIDATION_SECRET_API: WorkerCredentialKubernetesSecretApi = {
async readNamespacedSecret() { throw new Error('validation only'); },
async createNamespacedSecret() { throw new Error('validation only'); },
async replaceNamespacedSecret() { throw new Error('validation only'); },
async deleteNamespacedSecret() { throw new Error('validation only'); },
async listNamespacedSecret() { throw new Error('validation only'); },
};
const VALIDATION_DEPLOYMENT_API: WorkerCredentialKubernetesDeploymentApi = {
async readNamespacedDeployment() { throw new Error('validation only'); },
async replaceNamespacedDeployment() { throw new Error('validation only'); },
};
function jsonObject(value: unknown): JsonObject {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
}
return value as JsonObject;
}
function decodeJwtSegment(value: string): JsonObject {
try {
const bytes = Buffer.from(value, 'base64url');
if (bytes.toString('base64url') !== value) {
throw new Error('non-canonical base64url');
}
return jsonObject(JSON.parse(bytes.toString('utf8')));
} catch (error) {
if (error instanceof WorkerCredentialKubernetesTokenRequestUnavailableError) {
throw error;
}
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
}
}
function tokenEvidence(
response: TokenRequestResponse,
namespace: string,
serviceAccountName: string,
observedAtMs: number,
): Readonly<{ token: string; lifetimeSeconds: number }> {
if (
response?.apiVersion !== 'authentication.k8s.io/v1' ||
response.kind !== 'TokenRequest' ||
typeof response.status?.token !== 'string' ||
response.status.token.length < 1 ||
Buffer.byteLength(response.status.token, 'utf8') > MAX_TOKEN_BYTES
) {
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
}
const match = JWT.exec(response.status.token);
if (!match) {
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
}
const header = decodeJwtSegment(match[1]!);
const claims = decodeJwtSegment(match[2]!);
if (
typeof header.alg !== 'string' ||
!SAFE_JWT_ALGORITHM.test(header.alg) ||
header.alg.toLowerCase() === 'none' ||
claims.sub !== `system:serviceaccount:${namespace}:${serviceAccountName}` ||
!Number.isSafeInteger(claims.iat) ||
!Number.isSafeInteger(claims.exp)
) {
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
}
const issuedAtSeconds = claims.iat as number;
const expiresAtSeconds = claims.exp as number;
const lifetimeSeconds = expiresAtSeconds - issuedAtSeconds;
const expiration = response.status.expirationTimestamp;
const expirationMs = expiration instanceof Date
? expiration.getTime()
: typeof expiration === 'string'
? Date.parse(expiration)
: Number.NaN;
if (
lifetimeSeconds < MIN_USEFUL_TOKEN_SECONDS ||
lifetimeSeconds > WORKER_CREDENTIAL_KUBERNETES_TOKEN_REQUEST_SECONDS ||
!Number.isSafeInteger(expirationMs) ||
expirationMs !== expiresAtSeconds * 1_000 ||
expirationMs - observedAtMs < MIN_USEFUL_TOKEN_SECONDS * 1_000
) {
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
}
return Object.freeze({
token: response.status.token,
lifetimeSeconds,
});
}
function accessMatrix(
delivery: WorkerCredentialKubernetesDeliveryAdapterOptions,
identitySecretName: string,
serviceAccountName: string,
): Readonly<{
allowed: readonly AccessReviewAttributes[];
denied: readonly AccessReviewAttributes[];
}> {
const allowed: readonly AccessReviewAttributes[] = [
{ namespace: delivery.stageNamespace, verb: 'get', resource: 'secrets', name: 'stage' },
{ namespace: delivery.stageNamespace, verb: 'list', resource: 'secrets' },
{ namespace: delivery.stageNamespace, verb: 'create', resource: 'secrets' },
{ namespace: delivery.stageNamespace, verb: 'delete', resource: 'secrets', name: 'stage' },
{ namespace: delivery.namespace, verb: 'get', resource: 'secrets', name: delivery.targetSecretName },
{ namespace: delivery.namespace, verb: 'update', resource: 'secrets', name: delivery.targetSecretName },
{ namespace: delivery.namespace, verb: 'get', group: 'apps', resource: 'deployments', name: delivery.targetDeploymentName },
{ namespace: delivery.namespace, verb: 'update', group: 'apps', resource: 'deployments', name: delivery.targetDeploymentName },
];
const denied: readonly AccessReviewAttributes[] = [
{ namespace: delivery.stageNamespace, verb: 'update', resource: 'secrets', name: 'stage' },
{ namespace: delivery.stageNamespace, verb: 'patch', resource: 'secrets', name: 'stage' },
{ namespace: delivery.stageNamespace, verb: 'watch', resource: 'secrets' },
{ namespace: delivery.stageNamespace, verb: 'get', resource: 'configmaps', name: 'any' },
{ namespace: delivery.namespace, verb: 'list', resource: 'secrets' },
{ namespace: delivery.namespace, verb: 'get', resource: 'secrets', name: identitySecretName },
{ namespace: delivery.namespace, verb: 'create', resource: 'secrets' },
{ namespace: delivery.namespace, verb: 'delete', resource: 'secrets', name: delivery.targetSecretName },
{ namespace: delivery.namespace, verb: 'patch', resource: 'secrets', name: delivery.targetSecretName },
{ namespace: delivery.namespace, verb: 'watch', resource: 'secrets' },
{ namespace: delivery.namespace, verb: 'list', group: 'apps', resource: 'deployments' },
{ namespace: delivery.namespace, verb: 'get', group: 'apps', resource: 'deployments', name: 'other' },
{ namespace: delivery.namespace, verb: 'patch', group: 'apps', resource: 'deployments', name: delivery.targetDeploymentName },
{ namespace: delivery.namespace, verb: 'delete', group: 'apps', resource: 'deployments', name: delivery.targetDeploymentName },
{ namespace: delivery.namespace, verb: 'get', resource: 'pods' },
{ namespace: delivery.namespace, verb: 'list', resource: 'pods' },
{ namespace: delivery.namespace, verb: 'create', resource: 'pods', subresource: 'exec' },
{ namespace: delivery.namespace, verb: 'delete', resource: 'pods', name: 'any' },
{ namespace: delivery.stageNamespace, verb: 'create', resource: 'serviceaccounts', subresource: 'token', name: serviceAccountName },
{ verb: 'get', resource: 'namespaces', name: delivery.namespace },
];
return Object.freeze({ allowed, denied });
}
function issuerAccessMatrix(
delivery: WorkerCredentialKubernetesDeliveryAdapterOptions,
serviceAccountName: string,
): Readonly<{
allowed: readonly AccessReviewAttributes[];
denied: readonly AccessReviewAttributes[];
}> {
return Object.freeze({
allowed: [{
namespace: delivery.stageNamespace,
verb: 'create',
resource: 'serviceaccounts',
subresource: 'token',
name: serviceAccountName,
}],
denied: [
{
namespace: delivery.stageNamespace,
verb: 'create',
resource: 'serviceaccounts',
subresource: 'token',
name: 'other',
},
{ namespace: delivery.stageNamespace, verb: 'get', resource: 'secrets', name: 'any' },
{ namespace: delivery.stageNamespace, verb: 'list', resource: 'secrets' },
{ namespace: delivery.stageNamespace, verb: 'create', resource: 'secrets' },
{ namespace: delivery.namespace, verb: 'get', resource: 'secrets', name: delivery.targetSecretName },
{ namespace: delivery.namespace, verb: 'get', group: 'apps', resource: 'deployments', name: delivery.targetDeploymentName },
{ namespace: delivery.namespace, verb: 'get', resource: 'pods' },
{ verb: 'get', resource: 'namespaces', name: delivery.namespace },
],
});
}
async function assertAccess(
authorization: WorkerCredentialKubernetesAuthorizationApi,
expected: boolean,
checks: readonly AccessReviewAttributes[],
): Promise<void> {
for (const attributes of checks) {
let result;
try {
result = await authorization.createSelfSubjectAccessReview({
body: {
apiVersion: 'authorization.k8s.io/v1',
kind: 'SelfSubjectAccessReview',
spec: { resourceAttributes: attributes },
},
});
} catch {
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
}
if (result?.status?.allowed !== expected) {
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
}
}
}
export function createWorkerCredentialKubernetesTokenRequestSession(
tokenRequests: WorkerCredentialKubernetesTokenRequestApi,
issuerAuthorization: WorkerCredentialKubernetesAuthorizationApi,
createRestrictedClients: (
token: string,
) => WorkerCredentialKubernetesRestrictedClients,
options: WorkerCredentialKubernetesTokenRequestSessionOptions,
): WorkerCredentialKubernetesTokenRequestSession {
if (
!tokenRequests ||
typeof tokenRequests.createNamespacedServiceAccountToken !== 'function' ||
!issuerAuthorization ||
typeof issuerAuthorization.createSelfSubjectAccessReview !== 'function' ||
typeof createRestrictedClients !== 'function' ||
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
Object.keys(options).some((key) =>
!['serviceAccountName', 'identitySecretName', 'delivery', 'now'].includes(key)) ||
typeof options.serviceAccountName !== 'string' ||
!DNS_LABEL.test(options.serviceAccountName) ||
typeof options.identitySecretName !== 'string' ||
!DNS_SUBDOMAIN.test(options.identitySecretName) ||
Buffer.byteLength(options.identitySecretName, 'utf8') > 253 ||
(options.now !== undefined && typeof options.now !== 'function')
) {
throw new TypeError('Worker credential Kubernetes TokenRequest options are invalid');
}
const validation = new WorkerCredentialKubernetesDeliveryAdapter(
VALIDATION_SECRET_API,
VALIDATION_DEPLOYMENT_API,
options.delivery,
);
const now = options.now ?? Date.now;
const matrix = accessMatrix(
options.delivery,
options.identitySecretName,
options.serviceAccountName,
);
const issuerMatrix = issuerAccessMatrix(
options.delivery,
options.serviceAccountName,
);
return Object.freeze({
async withDelivery<T>(
operation: (
context: Readonly<WorkerCredentialKubernetesTokenRequestContext>,
) => Promise<T>,
): Promise<T> {
if (typeof operation !== 'function') {
throw new TypeError('Worker credential Kubernetes operation is invalid');
}
const requestedAtMs = now();
if (!Number.isSafeInteger(requestedAtMs) || requestedAtMs < 0) {
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
}
let response: TokenRequestResponse | undefined;
let issuedToken = '';
let clients: WorkerCredentialKubernetesRestrictedClients | undefined;
try {
await assertAccess(issuerAuthorization, true, issuerMatrix.allowed);
await assertAccess(issuerAuthorization, false, issuerMatrix.denied);
try {
response = await tokenRequests.createNamespacedServiceAccountToken({
name: options.serviceAccountName,
namespace: options.delivery.stageNamespace,
body: {
apiVersion: 'authentication.k8s.io/v1',
kind: 'TokenRequest',
spec: {
expirationSeconds:
WORKER_CREDENTIAL_KUBERNETES_TOKEN_REQUEST_SECONDS,
},
},
});
} catch {
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
}
const observedAtMs = now();
if (
!Number.isSafeInteger(observedAtMs) ||
observedAtMs < requestedAtMs
) {
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
}
const evidence = tokenEvidence(
response,
options.delivery.stageNamespace,
options.serviceAccountName,
observedAtMs,
);
issuedToken = evidence.token;
try {
clients = createRestrictedClients(issuedToken);
} catch {
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
} finally {
if (response.status) response.status.token = '';
issuedToken = '';
}
if (
!clients ||
!clients.authorization ||
typeof clients.authorization.createSelfSubjectAccessReview !== 'function' ||
typeof clients.dispose !== 'function'
) {
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
}
const delivery = new WorkerCredentialKubernetesDeliveryAdapter(
clients.secrets,
clients.deployments,
options.delivery,
);
if (delivery.deploymentTargetDigest !== validation.deploymentTargetDigest) {
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
}
await assertAccess(clients.authorization, true, matrix.allowed);
await assertAccess(clients.authorization, false, matrix.denied);
return await operation(Object.freeze({
delivery,
evidence: Object.freeze({
tokenLifetimeSeconds: evidence.lifetimeSeconds,
issuerAllowedChecks: issuerMatrix.allowed.length,
issuerDeniedChecks: issuerMatrix.denied.length,
allowedChecks: matrix.allowed.length,
deniedChecks: matrix.denied.length,
}),
}));
} finally {
if (response?.status) response.status.token = '';
issuedToken = '';
try {
await clients?.dispose();
} catch {
throw new WorkerCredentialKubernetesTokenRequestUnavailableError();
} finally {
clients = undefined;
response = undefined;
}
}
},
});
}
export function createWorkerCredentialKubernetesKubeConfigTokenRequestSession(
issuerKubeConfig: KubernetesConfig,
kubernetes: KubernetesModule,
options: WorkerCredentialKubernetesTokenRequestSessionOptions,
): WorkerCredentialKubernetesTokenRequestSession {
if (
!issuerKubeConfig ||
!kubernetes ||
typeof kubernetes !== 'object' ||
typeof kubernetes.KubeConfig !== 'function' ||
typeof kubernetes.CoreV1Api !== 'function' ||
typeof kubernetes.AppsV1Api !== 'function' ||
typeof kubernetes.AuthorizationV1Api !== 'function' ||
typeof issuerKubeConfig.getCurrentCluster !== 'function' ||
typeof issuerKubeConfig.makeApiClient !== 'function'
) {
throw new TypeError('Worker credential Kubernetes issuer kubeconfig is invalid');
}
const cluster = issuerKubeConfig.getCurrentCluster();
let server: URL;
try {
server = new URL(cluster?.server ?? '');
} catch {
throw new TypeError('Worker credential Kubernetes issuer cluster is invalid');
}
if (
!cluster ||
server.protocol !== 'https:' ||
server.username !== '' ||
server.password !== '' ||
server.hash !== '' ||
cluster.skipTLSVerify === true ||
(typeof cluster.caData !== 'string' && typeof cluster.caFile !== 'string')
) {
throw new TypeError('Worker credential Kubernetes issuer cluster is invalid');
}
const tokenRequestClient = issuerKubeConfig.makeApiClient(
kubernetes.CoreV1Api,
);
const issuerAuthorization = issuerKubeConfig.makeApiClient(
kubernetes.AuthorizationV1Api,
) as unknown as WorkerCredentialKubernetesAuthorizationApi;
const tokenRequests: WorkerCredentialKubernetesTokenRequestApi = {
async createNamespacedServiceAccountToken(request) {
return await tokenRequestClient.createNamespacedServiceAccountToken({
...request,
body: {
...request.body,
spec: {
audiences: [],
expirationSeconds: request.body.spec.expirationSeconds,
},
},
}) as unknown as TokenRequestResponse;
},
};
return createWorkerCredentialKubernetesTokenRequestSession(
tokenRequests,
issuerAuthorization,
(token) => {
const restricted = new kubernetes.KubeConfig();
restricted.loadFromOptions({
clusters: [{ ...cluster, name: 'ql3-worker-credential-delivery' }],
users: [{ name: 'ql3-worker-credential-delivery', token }],
contexts: [{
name: 'ql3-worker-credential-delivery',
cluster: 'ql3-worker-credential-delivery',
user: 'ql3-worker-credential-delivery',
namespace: options.delivery.stageNamespace,
}],
currentContext: 'ql3-worker-credential-delivery',
});
let active = true;
return {
secrets: restricted.makeApiClient(
kubernetes.CoreV1Api,
) as unknown as WorkerCredentialKubernetesSecretApi,
deployments: restricted.makeApiClient(
kubernetes.AppsV1Api,
) as unknown as WorkerCredentialKubernetesDeploymentApi,
authorization: restricted.makeApiClient(
kubernetes.AuthorizationV1Api,
) as unknown as WorkerCredentialKubernetesAuthorizationApi,
dispose() {
if (!active) return;
active = false;
for (const user of restricted.getUsers()) {
(user as { token?: string }).token = '';
}
restricted.setCurrentContext('disposed');
},
};
},
options,
);
}
@@ -0,0 +1,186 @@
/** Worker credential management client boundary. */
import {
executeClusterAuthenticatedManagementClient,
type ClusterAuthenticatedManagementClientResult,
type ClusterPluginPackageManagementClientConnectionOptions,
type ClusterPluginPackageManagementClientPaths,
} from '../management-support/pluginPackageManagementClient';
import {
normalizeClusterWorkerCredentialManagementCommand,
type ClusterWorkerCredentialManagementCommand,
type ClusterWorkerCredentialManagementTransportResult,
} from './management-server/workerCredentialManagementTransport';
const MANAGEMENT_PATH = '/api/v3/worker-credentials/management';
const CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
export type ClusterWorkerCredentialManagementClientPaths =
ClusterPluginPackageManagementClientPaths;
export type ClusterWorkerCredentialManagementClientConnectionOptions =
ClusterPluginPackageManagementClientConnectionOptions;
export type ClusterWorkerCredentialManagementClientResult =
ClusterAuthenticatedManagementClientResult<ClusterWorkerCredentialManagementTransportResult>;
function invalid(): never {
throw new Error('Worker credential management response is invalid');
}
function exactRecord(
value: unknown,
keys: readonly string[],
): Record<string, unknown> {
if (!value || typeof value !== 'object' || Array.isArray(value)) invalid();
const record = value as Record<string, unknown>;
const actual = Object.keys(record).sort();
const expected = [...keys].sort();
if (
actual.length !== expected.length ||
actual.some((key, index) => key !== expected[index])
) {
invalid();
}
return record;
}
function boundedScalar(value: unknown): void {
if (
value !== null &&
!(typeof value === 'boolean') &&
!(typeof value === 'number' && Number.isSafeInteger(value)) &&
!(
typeof value === 'string' &&
value.length <= 2_048 &&
!CONTROL_PATTERN.test(value)
)
) {
invalid();
}
}
function subject(value: unknown): void {
const record = exactRecord(value, ['type', 'id']);
if (record.type !== 'user') invalid();
boundedScalar(record.id);
}
function plan(value: unknown): void {
const record = exactRecord(value, [
'actionRef',
'authorityProjectId',
'action',
'target',
'requestedBy',
'plannedAtMs',
'expiresAtMs',
'previewDigest',
'planDigest',
]);
const target = exactRecord(record.target, [
'deliveryId',
'workerId',
'credentialId',
'previousCredentialId',
'credentialNotBeforeAtMs',
'credentialExpiresAtMs',
'deploymentTargetDigest',
'deploymentGeneration',
]);
for (const entry of Object.values(record)) {
if (entry !== record.target && entry !== record.requestedBy)
boundedScalar(entry);
}
for (const entry of Object.values(target)) boundedScalar(entry);
subject(record.requestedBy);
if (!['issue', 'rotate'].includes(String(record.action))) invalid();
}
function approval(value: unknown): void {
const record = exactRecord(value, [
'id',
'projectId',
'version',
'state',
'risk',
'decisionMode',
'requestedBy',
'requestedAtMs',
'expiresAtMs',
'decision',
'decisionReasonCode',
'decidedBy',
'decidedAtMs',
'dispatchId',
'consumedAtMs',
'actionType',
'actionRef',
'actionDigest',
'previewDigest',
]);
for (const entry of Object.values(record)) {
if (entry !== record.requestedBy && entry !== record.decidedBy)
boundedScalar(entry);
}
subject(record.requestedBy);
if (record.decidedBy !== null) subject(record.decidedBy);
if (
!/^worker_credential\.delivery\.(?:issue|rotate)$/.test(
String(record.actionType),
)
) {
invalid();
}
}
export function validateClusterWorkerCredentialManagementClientResult(
value: unknown,
command: Readonly<ClusterWorkerCredentialManagementCommand>,
): Readonly<ClusterWorkerCredentialManagementTransportResult> {
const operation = command.operation;
const keys =
operation === 'worker-credential.plan'
? ['schemaVersion', 'operation', 'status', 'plan']
: operation === 'worker-credential.propose'
? ['schemaVersion', 'operation', 'approvalStatus', 'plan', 'approval']
: operation === 'worker-credential.decide'
? ['schemaVersion', 'operation', 'status', 'approval']
: ['schemaVersion', 'operation', 'plan', 'approval', 'stale'];
const record = exactRecord(value, keys);
if (record.schemaVersion !== 1 || record.operation !== operation) invalid();
if (operation === 'worker-credential.plan') {
if (!['created', 'existing'].includes(String(record.status))) invalid();
plan(record.plan);
} else if (operation === 'worker-credential.propose') {
if (!['created', 'existing'].includes(String(record.approvalStatus)))
invalid();
plan(record.plan);
approval(record.approval);
} else if (operation === 'worker-credential.decide') {
if (!['decided', 'existing'].includes(String(record.status))) invalid();
approval(record.approval);
} else {
if (typeof record.stale !== 'boolean') invalid();
if (record.plan !== null) plan(record.plan);
if (record.approval !== null) approval(record.approval);
}
return Object.freeze(
record as unknown as ClusterWorkerCredentialManagementTransportResult,
);
}
const PROTOCOL = Object.freeze({
managementPath: MANAGEMENT_PATH,
clientCertificate: 'required' as const,
normalizeCommand: normalizeClusterWorkerCredentialManagementCommand,
validateResult: validateClusterWorkerCredentialManagementClientResult,
});
export async function executeClusterWorkerCredentialManagementClient(
paths: ClusterWorkerCredentialManagementClientPaths,
connectionOptions?: ClusterWorkerCredentialManagementClientConnectionOptions,
): Promise<Readonly<ClusterWorkerCredentialManagementClientResult>> {
return executeClusterAuthenticatedManagementClient(
paths,
PROTOCOL,
connectionOptions,
);
}
@@ -0,0 +1,96 @@
#!/usr/bin/env node
/** One-shot Worker credential management client CLI boundary. */
import { ClusterPluginPackageManagementClientRemoteError } from '../management-support/pluginPackageManagementClient';
import { executeClusterWorkerCredentialManagementClient } from './workerCredentialManagementClient';
const USAGE =
'Usage: ql3-worker-credential-client --config=/absolute/client.json --command=/absolute/command.json --assertion=/absolute/assertion.jwt';
function parseArguments(
argv: readonly string[],
): Readonly<{
configFile: string;
commandFile: string;
assertionFile: string;
}> | null {
if (argv.length !== 3) return null;
const values = new Map<string, string>();
for (const argument of argv) {
const match = /^--(config|command|assertion)=(\/.+)$/.exec(argument);
if (!match || values.has(match[1]!)) return null;
values.set(match[1]!, match[2]!);
}
if (
!values.has('config') ||
!values.has('command') ||
!values.has('assertion')
) {
return null;
}
return Object.freeze({
configFile: values.get('config')!,
commandFile: values.get('command')!,
assertionFile: values.get('assertion')!,
});
}
function failureFact(error: unknown): Readonly<Record<string, unknown>> {
const candidate = error as { readonly code?: unknown };
return Object.freeze({
schemaVersion: 1,
component: 'qinglong3-worker-credential-management-client',
event: 'command_failed',
code:
typeof candidate?.code === 'string' && candidate.code.length <= 128
? candidate.code
: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_CLIENT_FAILED',
...(error instanceof ClusterPluginPackageManagementClientRemoteError
? {
statusCode: error.statusCode,
responseCode: error.responseCode,
requestId: error.requestId,
...(error.retryAfterSeconds === null
? {}
: { retryAfterSeconds: error.retryAfterSeconds }),
}
: {}),
});
}
async function run(argv: readonly string[]): Promise<void> {
if (argv.length === 1 && (argv[0] === '--help' || argv[0] === '-h')) {
process.stdout.write(`${USAGE}\n`);
return;
}
const paths = parseArguments(argv);
if (!paths) {
process.stderr.write(
`${JSON.stringify({
schemaVersion: 1,
component: 'qinglong3-worker-credential-management-client',
event: 'usage_invalid',
code: 'QL3_WORKER_CREDENTIAL_MANAGEMENT_CLIENT_USAGE_INVALID',
})}\n`,
);
process.exitCode = 64;
return;
}
try {
const result = await executeClusterWorkerCredentialManagementClient(paths);
process.stdout.write(
`${JSON.stringify({
schemaVersion: 1,
component: 'qinglong3-worker-credential-management-client',
event: 'command_completed',
requestId: result.requestId,
result: result.result,
})}\n`,
);
} catch (error) {
process.stderr.write(`${JSON.stringify(failureFact(error))}\n`);
process.exitCode = 1;
}
}
void run(process.argv.slice(2));
@@ -0,0 +1,546 @@
/** Approved Worker credential management execution boundary. */
import { createHash } from 'node:crypto';
import {
PostgresApprovedActionExecutionRepository,
PostgresApprovalRequestRepository,
PostgresProjectPolicyRepository,
PostgresWorkerCredentialAdministrationRepository,
PostgresWorkerCredentialManagementPlanReader,
assertPostgresWorkerCredentialExecutorSchemaReady,
type PostgresSchemaReadinessReport,
} from '@qinglong/cluster-postgres/worker-credential-executor';
import type {
OpenPostgresDatabase,
PostgresDatabaseResource,
PostgresPool,
} from '@qinglong/runtime-core';
import {
normalizeApprovalRequestRecord,
type ApprovedActionBinding,
type ApprovedActionDispatchRecord,
} from '@qinglong/runtime-core/approved-action';
import type { ApprovedActionExecutionRecord } from '@qinglong/runtime-core/approved-action-execution';
import { ProjectPolicyEngine } from '@qinglong/runtime-core/project-policy';
import type {
SecurityPolicyFence,
SecurityPrincipal,
SecuritySubject,
} from '@qinglong/runtime-core/security';
import type { SecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
import {
normalizeWorkerCredentialManagementPlan,
type WorkerCredentialManagementPlan,
} from '@qinglong/runtime-core/worker-credential-management-plan';
import {
createRecoverableWorkerCredentialIssuer,
type RecoverableWorkerCredentialIssueResult,
} from './workerCredentialDelivery';
import type {
WorkerCredentialKubernetesTokenRequestEvidence,
WorkerCredentialKubernetesTokenRequestSession,
} from './workerCredentialKubernetesTokenRequest';
import {
WorkerCredentialManagementConflictError,
WorkerCredentialManagementRequestError,
WorkerCredentialManagementUnavailableError,
} from './management-server/workerCredentialManagement';
const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const ACTION_REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;
const EXECUTOR_SUBJECT = Object.freeze({
type: 'system' as const,
id: 'cluster_worker_credential_executor',
});
const EXECUTOR_AUTHENTICATION_ID = 'cluster_worker_credential_executor_v1';
const EXECUTOR_PRINCIPAL_LIFETIME_MS = 15 * 60 * 1000;
const EXECUTION_LEASE_DURATION_MS = 10 * 60 * 1000;
const EXECUTION_OWNER = 'cluster_worker_credential_executor';
const EXECUTION_RESULT_CODE = 'worker_credential_published';
export interface RunClusterWorkerCredentialExecutionOptions {
readonly openDatabase: OpenPostgresDatabase;
readonly tokenRequestSession: WorkerCredentialKubernetesTokenRequestSession;
readonly workerCredentialPepper: string;
readonly actionRef: string;
readonly approvalRequestId: string;
readonly consumptionId: string;
readonly dispatchId: string;
readonly auditEventId: string;
readonly confirmAuthorization: () => void | Promise<void>;
readonly now?: () => number;
readonly randomBytes?: (size: number) => Buffer;
}
export interface ClusterWorkerCredentialExecutionRun {
readonly database: PostgresSchemaReadinessReport;
readonly approval: Readonly<ApprovedActionDispatchRecord>;
readonly execution: Readonly<ApprovedActionExecutionRecord>;
readonly result: Readonly<RecoverableWorkerCredentialIssueResult>;
readonly tokenRequest: Readonly<WorkerCredentialKubernetesTokenRequestEvidence> | null;
}
type Row = Record<string, unknown>;
function exactOptions(value: RunClusterWorkerCredentialExecutionOptions): void {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new WorkerCredentialManagementRequestError(
'execution options must be an object',
);
}
const allowed = new Set([
'actionRef',
'approvalRequestId',
'auditEventId',
'confirmAuthorization',
'consumptionId',
'dispatchId',
'now',
'openDatabase',
'randomBytes',
'tokenRequestSession',
'workerCredentialPepper',
]);
if (Object.keys(value).some((key) => !allowed.has(key))) {
throw new WorkerCredentialManagementRequestError(
'execution options shape is invalid',
);
}
}
function identifier(value: unknown, label: string): string {
if (typeof value !== 'string' || !IDENTIFIER_PATTERN.test(value)) {
throw new WorkerCredentialManagementRequestError(`${label} is invalid`);
}
return value;
}
function actionRef(value: unknown): string {
if (typeof value !== 'string' || !ACTION_REF_PATTERN.test(value)) {
throw new WorkerCredentialManagementRequestError('actionRef is invalid');
}
return value;
}
function currentTime(now: () => number): number {
const value = now();
if (!Number.isSafeInteger(value) || value < 0) {
throw new WorkerCredentialManagementUnavailableError();
}
return value;
}
function same(left: unknown, right: unknown): boolean {
return JSON.stringify(left) === JSON.stringify(right);
}
function binding(
plan: Readonly<WorkerCredentialManagementPlan>,
): Readonly<ApprovedActionBinding> {
return Object.freeze({
permission: 'worker.manage',
actionType: `worker_credential.delivery.${plan.action}`,
actionRef: plan.actionRef,
actionDigest: plan.planDigest,
previewDigest: plan.previewDigest,
});
}
function audit(
eventId: string,
requestId: string,
projectId: string,
fence: Readonly<SecurityPolicyFence>,
occurredAtMs: number,
): Readonly<SecurityAuditRecord> {
return Object.freeze({
eventId,
requestId,
operationId: 'approval.consume',
projectId,
subject: EXECUTOR_SUBJECT,
authenticationId: EXECUTOR_AUTHENTICATION_ID,
outcome: 'allowed',
reasons: Object.freeze(['worker_credential_review']),
fence,
occurredAtMs,
});
}
function executorPrincipal(nowMs: number): Readonly<SecurityPrincipal> {
return Object.freeze({
subject: EXECUTOR_SUBJECT,
authenticationId: EXECUTOR_AUTHENTICATION_ID,
authenticatedAtMs: nowMs,
expiresAtMs: nowMs + EXECUTOR_PRINCIPAL_LIFETIME_MS,
assurance: 'service' as const,
});
}
function executionResultDigest(
plan: Readonly<WorkerCredentialManagementPlan>,
result: Readonly<RecoverableWorkerCredentialIssueResult>,
): string {
const delivery = result.delivery;
if (
!delivery ||
(delivery.state !== 'published' &&
delivery.state !== 'observed' &&
delivery.state !== 'previous_revoked') ||
delivery.deliveryId !== plan.target.deliveryId ||
delivery.workerId !== plan.target.workerId ||
delivery.credentialId !== plan.target.credentialId ||
delivery.previousCredentialId !== plan.target.previousCredentialId ||
delivery.deploymentTargetDigest !== plan.target.deploymentTargetDigest ||
delivery.deploymentGeneration !== plan.target.deploymentGeneration ||
typeof delivery.publicationDigest !== 'string'
) {
throw new WorkerCredentialManagementConflictError(
'credential delivery does not match approved execution',
);
}
return createHash('sha256')
.update('qinglong/worker-credential-execution-result@v1\0', 'utf8')
.update(
JSON.stringify({
deliveryId: delivery.deliveryId,
credentialId: delivery.credentialId,
workerId: delivery.workerId,
deploymentGeneration: delivery.deploymentGeneration,
publicationDigest: delivery.publicationDigest,
}),
'utf8',
)
.digest('hex');
}
async function assertPredecessor(
pool: PostgresPool,
plan: Readonly<WorkerCredentialManagementPlan>,
observedAtMs: number,
): Promise<void> {
if (plan.action === 'issue') return;
const result = await pool.query<Row>(
`SELECT state, worker_id AS "workerId", expires_at_ms AS "expiresAtMs"
FROM "ql3"."worker_credentials"
WHERE credential_id = $1
ORDER BY version DESC
LIMIT 1`,
[plan.target.previousCredentialId],
);
const row = result.rows[0];
const expiresAtMs =
typeof row?.expiresAtMs === 'number'
? row.expiresAtMs
: typeof row?.expiresAtMs === 'string' && /^\d+$/.test(row.expiresAtMs)
? Number(row.expiresAtMs)
: Number.NaN;
if (
result.rows.length !== 1 ||
row?.state !== 'active' ||
row.workerId !== plan.target.workerId ||
!Number.isSafeInteger(expiresAtMs) ||
expiresAtMs <= observedAtMs
) {
throw new WorkerCredentialManagementConflictError(
'rotation predecessor is not active for the target Worker',
);
}
}
async function closeDatabase(
database: PostgresDatabaseResource | undefined,
failure: unknown,
): Promise<void> {
if (!database) {
if (failure !== undefined) throw failure;
return;
}
try {
await database.close();
} catch (closeError) {
if (failure !== undefined) {
throw new AggregateError(
[failure, closeError],
'Worker credential execution failed and PostgreSQL did not close',
);
}
throw closeError;
}
if (failure !== undefined) throw failure;
}
export async function runClusterWorkerCredentialExecution(
options: RunClusterWorkerCredentialExecutionOptions,
): Promise<Readonly<ClusterWorkerCredentialExecutionRun>> {
exactOptions(options);
if (
typeof options.openDatabase !== 'function' ||
!options.tokenRequestSession ||
typeof options.tokenRequestSession.withDelivery !== 'function' ||
typeof options.confirmAuthorization !== 'function' ||
typeof options.workerCredentialPepper !== 'string' ||
(options.now !== undefined && typeof options.now !== 'function') ||
(options.randomBytes !== undefined &&
typeof options.randomBytes !== 'function')
) {
throw new WorkerCredentialManagementRequestError(
'execution dependency is invalid',
);
}
const requestedActionRef = actionRef(options.actionRef);
const approvalRequestId = identifier(
options.approvalRequestId,
'approvalRequestId',
);
const consumptionId = identifier(options.consumptionId, 'consumptionId');
const dispatchId = identifier(options.dispatchId, 'dispatchId');
const auditEventId = identifier(options.auditEventId, 'auditEventId');
const now = options.now ?? Date.now;
let database: PostgresDatabaseResource | undefined;
let failure: unknown;
let run: Readonly<ClusterWorkerCredentialExecutionRun> | undefined;
try {
await options.confirmAuthorization();
database = await options.openDatabase();
const evidence = await assertPostgresWorkerCredentialExecutorSchemaReady(
database.pool,
);
const plans = new PostgresWorkerCredentialManagementPlanReader(
database.pool,
);
const planValue = await plans.findByActionRef(requestedActionRef);
if (!planValue) {
throw new WorkerCredentialManagementConflictError('plan does not exist');
}
const plan = normalizeWorkerCredentialManagementPlan(planValue);
const approvals = new PostgresApprovalRequestRepository(database.pool);
const approvalValue = await approvals.findById(approvalRequestId);
if (!approvalValue) {
throw new WorkerCredentialManagementConflictError(
'approval does not exist',
);
}
let approval = normalizeApprovalRequestRecord(approvalValue);
const approvedAction = binding(plan);
if (
approval.projectId !== plan.authorityProjectId ||
approval.decisionMode !== 'separation_of_duty' ||
!same(approval.action, approvedAction) ||
!same(approval.requestedBy, plan.requestedBy)
) {
throw new WorkerCredentialManagementConflictError(
'approval does not match durable plan',
);
}
let dispatch: Readonly<ApprovedActionDispatchRecord> | null = null;
if (approval.version === 2 && approval.state === 'approved') {
const policy = new ProjectPolicyEngine(
new PostgresProjectPolicyRepository(database.pool),
);
const decision = await policy.decide({
subject: plan.requestedBy,
projectId: plan.authorityProjectId,
permission: 'worker.manage',
});
if (
(decision.effect !== 'allow' &&
decision.effect !== 'require_approval') ||
decision.fence === null
) {
throw new WorkerCredentialManagementConflictError(
'requester is no longer authorized',
);
}
const consumedAtMs = currentTime(now);
const consumed = await approvals.consume({
requestId: approvalRequestId,
expectedVersion: 2,
consumptionId,
dispatchId,
action: approvedAction,
requestedBy: plan.requestedBy,
consumedBy: EXECUTOR_SUBJECT,
consumedAtMs,
authorizationFence: decision.fence,
audit: audit(
auditEventId,
approvalRequestId,
plan.authorityProjectId,
decision.fence,
consumedAtMs,
),
});
approval = consumed.request;
dispatch = consumed.dispatch;
} else if (approval.version === 3 && approval.state === 'consumed') {
dispatch = await approvals.findDispatchById(dispatchId);
}
if (
approval.version !== 3 ||
approval.state !== 'consumed' ||
approval.consumptionId !== consumptionId ||
approval.dispatchId !== dispatchId ||
!dispatch ||
dispatch.id !== dispatchId ||
!same(dispatch.action, approvedAction) ||
!same(dispatch.requestedBy, plan.requestedBy) ||
!same(dispatch.approvedBy, approval.decidedBy) ||
!same(dispatch.consumedBy, EXECUTOR_SUBJECT)
) {
throw new WorkerCredentialManagementConflictError(
'approval consumption does not match execution',
);
}
const authority = new PostgresWorkerCredentialAdministrationRepository(
database.pool,
);
const executions = new PostgresApprovedActionExecutionRepository(
database.pool,
);
let executionSnapshot = await executions.findExecutionByDispatchId(
dispatchId,
);
if (!executionSnapshot || !same(executionSnapshot.dispatch, dispatch)) {
throw new WorkerCredentialManagementConflictError(
'durable execution baseline does not match dispatch',
);
}
if (executionSnapshot.execution.status === 'succeeded') {
const resolved = await authority.resolveDelivered(plan.target.deliveryId);
const result = Object.freeze({
status: 'existing' as const,
delivery: resolved?.delivery ?? null,
});
const resultDigest = executionResultDigest(plan, result);
if (
executionSnapshot.execution.resultMutationId !==
plan.target.deliveryId ||
executionSnapshot.execution.resultCode !== EXECUTION_RESULT_CODE ||
executionSnapshot.execution.resultDigest !== resultDigest
) {
throw new WorkerCredentialManagementConflictError(
'durable execution result does not match credential delivery',
);
}
run = Object.freeze({
database: evidence,
approval: dispatch,
execution: executionSnapshot.execution,
result,
tokenRequest: null,
});
await closeDatabase(database, undefined);
return run;
}
const executionNowMs = currentTime(now);
if (
executionNowMs > plan.expiresAtMs ||
executionNowMs >= dispatch.expiresAtMs ||
executionNowMs >= plan.target.credentialExpiresAtMs
) {
throw new WorkerCredentialManagementConflictError(
'approved execution window expired',
);
}
await assertPredecessor(database.pool, plan, executionNowMs);
await options.confirmAuthorization();
if (
executionSnapshot.execution.status === 'pending' ||
executionSnapshot.execution.status === 'retry_wait' ||
(executionSnapshot.execution.status === 'leased' &&
executionSnapshot.execution.leaseExpiresAtMs !== null &&
executionSnapshot.execution.leaseExpiresAtMs <= executionNowMs)
) {
const claimed = await executions.claimExecution({
dispatchId,
owner: EXECUTION_OWNER,
leaseToken: consumptionId,
nowMs: executionNowMs,
leaseDurationMs: EXECUTION_LEASE_DURATION_MS,
});
if (claimed.status !== 'claimed') {
throw new WorkerCredentialManagementConflictError(
'approved execution could not be claimed',
);
}
executionSnapshot = claimed.snapshot;
}
if (
(executionSnapshot.execution.status !== 'leased' &&
executionSnapshot.execution.status !== 'executing') ||
executionSnapshot.execution.leaseOwner !== EXECUTION_OWNER ||
executionSnapshot.execution.leaseToken !== consumptionId ||
executionSnapshot.execution.leaseExpiresAtMs === null ||
executionSnapshot.execution.leaseExpiresAtMs <= executionNowMs
) {
throw new WorkerCredentialManagementConflictError(
'approved execution lease does not match caller',
);
}
if (executionSnapshot.execution.status === 'leased') {
executionSnapshot = await executions.startExecution({
dispatchId,
approvalRequestId,
actionDigest: approvedAction.actionDigest,
owner: EXECUTION_OWNER,
leaseToken: consumptionId,
expectedVersion: executionSnapshot.execution.version,
startedAtMs: executionNowMs,
});
}
const sessionResult = await options.tokenRequestSession.withDelivery(
async ({ delivery, evidence: tokenRequest }) => {
const issuer = createRecoverableWorkerCredentialIssuer(
authority,
delivery,
options.workerCredentialPepper,
{
now,
...(options.randomBytes
? { randomBytes: options.randomBytes }
: {}),
},
);
const result = await issuer.issue({
mutationId: plan.target.deliveryId,
requestId: approvalRequestId,
expectedCurrentVersion: 0,
credentialId: plan.target.credentialId,
workerId: plan.target.workerId,
principal: executorPrincipal(currentTime(now)),
notBeforeAtMs: plan.target.credentialNotBeforeAtMs,
expiresAtMs: plan.target.credentialExpiresAtMs,
previousCredentialId: plan.target.previousCredentialId,
deploymentTargetDigest: plan.target.deploymentTargetDigest,
deploymentGeneration: plan.target.deploymentGeneration,
});
return Object.freeze({ result, tokenRequest });
},
);
const completed = await executions.completeExecution({
dispatchId,
owner: EXECUTION_OWNER,
leaseToken: consumptionId,
expectedVersion: executionSnapshot.execution.version,
resultMutationId: plan.target.deliveryId,
outcome: 'succeeded',
resultCode: EXECUTION_RESULT_CODE,
resultDigest: executionResultDigest(plan, sessionResult.result),
completedAtMs: currentTime(now),
});
run = Object.freeze({
database: evidence,
approval: dispatch,
execution: completed.execution,
result: sessionResult.result,
tokenRequest: sessionResult.tokenRequest,
});
} catch (error) {
failure = error;
}
await closeDatabase(database, failure);
if (!run) {
throw new WorkerCredentialManagementUnavailableError();
}
return run;
}