mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 02:27:44 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
+247
@@ -0,0 +1,247 @@
|
||||
// Remote Worker Secret delivery authority persistence is owned by this domain.
|
||||
import {
|
||||
RemoteWorkerSecretDeliveryFenceRejectedError,
|
||||
RemoteWorkerSecretDeliveryUnavailableError,
|
||||
normalizeRemoteWorkerSecretDeliveryCommand,
|
||||
type RemoteWorkerSecretDeliveryAuthority,
|
||||
type RemoteWorkerSecretDeliveryAuthorityRepository,
|
||||
type RemoteWorkerSecretDeliveryCommand,
|
||||
} from '@qinglong/runtime-core/remote-secret-delivery';
|
||||
import {
|
||||
normalizeClusterTaskExecutionRevision,
|
||||
type ClusterTaskExecutionRevision,
|
||||
} from '@qinglong/runtime-core/cluster-execution-revision';
|
||||
import { digestRunDispatchLeaseToken } from '@qinglong/runtime-core/run-dispatch-lease';
|
||||
import type { PostgresClient, PostgresPool } from '@qinglong/runtime-core';
|
||||
import { lockAttemptAuthority } from '../run/attemptAuthorityLock';
|
||||
|
||||
type Row = Record<string, unknown>;
|
||||
|
||||
function text(row: Row, key: string): string {
|
||||
const value = row[key];
|
||||
if (typeof value !== 'string' || value.length < 1) {
|
||||
throw new RemoteWorkerSecretDeliveryUnavailableError();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function integer(row: Row, key: string): number {
|
||||
const raw = row[key];
|
||||
const value = typeof raw === 'string' && /^\d+$/.test(raw) ? Number(raw) : raw;
|
||||
if (typeof value !== 'number' || !Number.isSafeInteger(value)) {
|
||||
throw new RemoteWorkerSecretDeliveryUnavailableError();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function executionRevision(row: Row): ClusterTaskExecutionRevision {
|
||||
const plan = row.planJson;
|
||||
if (!plan || typeof plan !== 'object' || Array.isArray(plan)) {
|
||||
throw new RemoteWorkerSecretDeliveryUnavailableError();
|
||||
}
|
||||
const value = plan as Record<string, unknown>;
|
||||
const keys = Object.keys(value);
|
||||
if (
|
||||
!keys.includes('command') ||
|
||||
!keys.includes('environment') ||
|
||||
keys.some((key) =>
|
||||
!['command', 'environment', 'placement', 'timeoutMs', 'workingDirectory']
|
||||
.includes(key))
|
||||
) throw new RemoteWorkerSecretDeliveryUnavailableError();
|
||||
return normalizeClusterTaskExecutionRevision({
|
||||
projectId: text(row, 'revisionProjectId'),
|
||||
taskId: text(row, 'revisionTaskId'),
|
||||
sourceRevision: integer(row, 'sourceRevision'),
|
||||
taskRevision: text(row, 'revisionTaskRevision'),
|
||||
sourceContentDigest: text(row, 'sourceContentDigest'),
|
||||
executorType: text(row, 'revisionExecutorType') as 'remote_worker',
|
||||
planSchema: text(row, 'planSchema') as 'qinglong/command-execution@v1',
|
||||
command: value.command as ClusterTaskExecutionRevision['command'],
|
||||
environment: value.environment as ClusterTaskExecutionRevision['environment'],
|
||||
...(value.workingDirectory === undefined
|
||||
? {}
|
||||
: { workingDirectory: value.workingDirectory as string }),
|
||||
...(value.timeoutMs === undefined
|
||||
? {}
|
||||
: { timeoutMs: value.timeoutMs as number }),
|
||||
...(value.placement === undefined
|
||||
? {}
|
||||
: { placement: value.placement as NonNullable<ClusterTaskExecutionRevision['placement']> }),
|
||||
contentDigest: text(row, 'revisionContentDigest'),
|
||||
createdAtMs: integer(row, 'revisionCreatedAtMs'),
|
||||
});
|
||||
}
|
||||
|
||||
async function begin(client: PostgresClient): Promise<void> {
|
||||
await client.query('BEGIN');
|
||||
await client.query("SET LOCAL statement_timeout = '5s'");
|
||||
await client.query("SET LOCAL lock_timeout = '1s'");
|
||||
await client.query("SET LOCAL idle_in_transaction_session_timeout = '10s'");
|
||||
}
|
||||
|
||||
export class PostgresRemoteWorkerSecretDeliveryAuthorityRepository
|
||||
implements RemoteWorkerSecretDeliveryAuthorityRepository {
|
||||
constructor(private readonly pool: PostgresPool) {
|
||||
if (!pool || typeof pool.connect !== 'function') {
|
||||
throw new TypeError('PostgreSQL remote Secret delivery pool is invalid');
|
||||
}
|
||||
}
|
||||
|
||||
async authorize(
|
||||
input: RemoteWorkerSecretDeliveryCommand,
|
||||
): Promise<Readonly<RemoteWorkerSecretDeliveryAuthority>> {
|
||||
const command = normalizeRemoteWorkerSecretDeliveryCommand(input);
|
||||
const client = await this.pool.connect();
|
||||
try {
|
||||
await begin(client);
|
||||
await lockAttemptAuthority(client, command.attemptId);
|
||||
const result = await client.query<Row>(
|
||||
`WITH observation AS MATERIALIZED (
|
||||
SELECT floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint
|
||||
AS observed_at_ms
|
||||
)
|
||||
SELECT observation.observed_at_ms AS "observedAtMs",
|
||||
run.id AS "runId", run.project_id AS "runProjectId",
|
||||
run.task_id AS "runTaskId", run.task_revision AS "runTaskRevision",
|
||||
run.status AS "runStatus", run.execution_owner AS "executionOwner",
|
||||
run.cancel_requested_at_ms AS "cancelRequestedAtMs",
|
||||
attempt.status AS "attemptStatus",
|
||||
attempt.executor_type AS "attemptExecutorType",
|
||||
attempt.worker_id AS "attemptWorkerId",
|
||||
attempt.worker_session_id AS "attemptWorkerSessionId",
|
||||
attempt.worker_generation AS "attemptWorkerGeneration",
|
||||
attempt.lease_token_digest AS "attemptLeaseTokenDigest",
|
||||
attempt.lease_generation AS "attemptLeaseGeneration",
|
||||
attempt.lease_version AS "attemptLeaseVersion",
|
||||
attempt.offer_id AS "attemptOfferId",
|
||||
session.session_id AS "sessionId",
|
||||
session.generation AS "sessionGeneration",
|
||||
session.status AS "sessionStatus",
|
||||
session.lease_expires_at_ms AS "sessionExpiresAtMs",
|
||||
lease.run_id AS "leaseRunId", lease.status AS "leaseStatus",
|
||||
lease.version AS "leaseVersion",
|
||||
lease.lease_generation AS "leaseGeneration",
|
||||
lease.worker_id AS "leaseWorkerId",
|
||||
lease.worker_session_id AS "leaseWorkerSessionId",
|
||||
lease.worker_generation AS "leaseWorkerGeneration",
|
||||
lease.lease_token_digest AS "leaseTokenDigest",
|
||||
lease.offer_id AS "leaseOfferId",
|
||||
lease.expires_at_ms AS "leaseExpiresAtMs",
|
||||
revision.project_id AS "revisionProjectId",
|
||||
revision.task_id AS "revisionTaskId",
|
||||
revision.source_revision AS "sourceRevision",
|
||||
revision.task_revision AS "revisionTaskRevision",
|
||||
revision.source_content_digest AS "sourceContentDigest",
|
||||
revision.executor_type AS "revisionExecutorType",
|
||||
revision.plan_schema AS "planSchema",
|
||||
revision.plan_json AS "planJson",
|
||||
revision.content_digest AS "revisionContentDigest",
|
||||
revision.created_at_ms AS "revisionCreatedAtMs"
|
||||
FROM observation
|
||||
JOIN "ql3"."run_attempts" AS attempt ON attempt.id = $1
|
||||
JOIN "ql3"."runs" AS run ON run.id = attempt.run_id
|
||||
JOIN "ql3"."run_dispatch_leases" AS lease
|
||||
ON lease.attempt_id = attempt.id
|
||||
JOIN "ql3"."worker_sessions" AS session
|
||||
ON session.worker_id = lease.worker_id
|
||||
JOIN "ql3"."task_execution_revisions" AS revision
|
||||
ON revision.project_id = run.project_id
|
||||
AND revision.task_id = run.task_id
|
||||
AND revision.task_revision = run.task_revision
|
||||
AND revision.executor_type = 'remote_worker'`,
|
||||
[command.attemptId],
|
||||
);
|
||||
if (result.rows.length > 1) {
|
||||
throw new RemoteWorkerSecretDeliveryUnavailableError();
|
||||
}
|
||||
const row = result.rows[0];
|
||||
if (!row) {
|
||||
throw new RemoteWorkerSecretDeliveryFenceRejectedError('authority_mismatch');
|
||||
}
|
||||
const observedAtMs = integer(row, 'observedAtMs');
|
||||
const tokenDigest = digestRunDispatchLeaseToken(command.leaseToken);
|
||||
const matches =
|
||||
row.runId === command.runId &&
|
||||
row.runProjectId === command.projectId &&
|
||||
row.runTaskId === command.taskId &&
|
||||
row.runTaskRevision === command.taskRevision &&
|
||||
row.runStatus === 'dispatching' &&
|
||||
row.executionOwner === 'runtime' &&
|
||||
row.cancelRequestedAtMs === null &&
|
||||
row.attemptStatus === 'starting' &&
|
||||
row.attemptExecutorType === 'remote_worker' &&
|
||||
row.attemptWorkerId === command.workerId &&
|
||||
row.attemptWorkerSessionId === command.workerSessionId &&
|
||||
integer(row, 'attemptWorkerGeneration') === command.workerGeneration &&
|
||||
row.attemptLeaseTokenDigest === tokenDigest &&
|
||||
integer(row, 'attemptLeaseGeneration') === command.leaseGeneration &&
|
||||
integer(row, 'attemptLeaseVersion') === command.expectedLeaseVersion &&
|
||||
row.attemptOfferId === command.offerId &&
|
||||
row.sessionId === command.workerSessionId &&
|
||||
integer(row, 'sessionGeneration') === command.workerGeneration &&
|
||||
(row.sessionStatus === 'online' || row.sessionStatus === 'draining') &&
|
||||
integer(row, 'sessionExpiresAtMs') > observedAtMs &&
|
||||
row.leaseRunId === command.runId &&
|
||||
row.leaseStatus === 'leased' &&
|
||||
integer(row, 'leaseVersion') === command.expectedLeaseVersion &&
|
||||
integer(row, 'leaseGeneration') === command.leaseGeneration &&
|
||||
row.leaseWorkerId === command.workerId &&
|
||||
row.leaseWorkerSessionId === command.workerSessionId &&
|
||||
integer(row, 'leaseWorkerGeneration') === command.workerGeneration &&
|
||||
row.leaseTokenDigest === tokenDigest &&
|
||||
row.leaseOfferId === command.offerId &&
|
||||
integer(row, 'leaseExpiresAtMs') > observedAtMs;
|
||||
if (!matches) {
|
||||
throw new RemoteWorkerSecretDeliveryFenceRejectedError('authority_mismatch');
|
||||
}
|
||||
let revision: ClusterTaskExecutionRevision;
|
||||
try {
|
||||
revision = executionRevision(row);
|
||||
} catch (error) {
|
||||
if (error instanceof RemoteWorkerSecretDeliveryUnavailableError) throw error;
|
||||
throw new RemoteWorkerSecretDeliveryUnavailableError();
|
||||
}
|
||||
const expectedRefs = Object.freeze([
|
||||
...new Set(revision.environment.flatMap((binding) =>
|
||||
binding.kind === 'secret' ? [binding.secretRef] : [])),
|
||||
]);
|
||||
if (
|
||||
revision.projectId !== command.projectId ||
|
||||
revision.taskId !== command.taskId ||
|
||||
revision.taskRevision !== command.taskRevision ||
|
||||
revision.contentDigest !== command.executionDigest ||
|
||||
JSON.stringify(expectedRefs) !== JSON.stringify(command.secretRefs)
|
||||
) {
|
||||
throw new RemoteWorkerSecretDeliveryFenceRejectedError(
|
||||
'secret_scope_mismatch',
|
||||
);
|
||||
}
|
||||
const authority = Object.freeze({
|
||||
workerId: command.workerId,
|
||||
workerSessionId: command.workerSessionId,
|
||||
workerGeneration: command.workerGeneration,
|
||||
runId: command.runId,
|
||||
attemptId: command.attemptId,
|
||||
projectId: command.projectId,
|
||||
taskId: command.taskId,
|
||||
taskRevision: command.taskRevision,
|
||||
executionDigest: command.executionDigest,
|
||||
offerId: command.offerId,
|
||||
leaseGeneration: command.leaseGeneration,
|
||||
leaseVersion: command.expectedLeaseVersion,
|
||||
secretRefs: expectedRefs,
|
||||
});
|
||||
await client.query('COMMIT');
|
||||
return authority;
|
||||
} catch (error) {
|
||||
try { await client.query('ROLLBACK'); } catch { /* preserve root */ }
|
||||
if (
|
||||
error instanceof RemoteWorkerSecretDeliveryFenceRejectedError ||
|
||||
error instanceof RemoteWorkerSecretDeliveryUnavailableError
|
||||
) throw error;
|
||||
throw new RemoteWorkerSecretDeliveryUnavailableError();
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
+1121
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
// Worker Credential executor composition is owned by this PostgreSQL domain.
|
||||
export {
|
||||
PgPoolBinding,
|
||||
createPostgresDatabaseOpener,
|
||||
isPostgresTlsDnsServername,
|
||||
type OpenPostgresDatabaseOptions,
|
||||
type PostgresConnectionOptions,
|
||||
type PostgresDatabaseRole,
|
||||
type PostgresPoolOptions,
|
||||
type PostgresTlsOptions,
|
||||
type QingLongPostgresClient,
|
||||
type QingLongPostgresDatabaseResource,
|
||||
type QingLongPostgresPool,
|
||||
type QingLongPostgresQueryResult,
|
||||
} from '../connection/pool';
|
||||
|
||||
export {
|
||||
PostgresConnectionEnvironmentError,
|
||||
loadPostgresConnectionEnvironment,
|
||||
type PostgresConnectionEnvironment,
|
||||
type PostgresConnectionEnvironmentKeys,
|
||||
} from '../connection/connectionEnvironment';
|
||||
|
||||
export {
|
||||
POSTGRES_CA_FILE_ERROR_CODES,
|
||||
POSTGRES_CA_MAX_CERTIFICATES,
|
||||
POSTGRES_CA_MAX_FILE_BYTES,
|
||||
PostgresCertificateAuthorityFileError,
|
||||
inspectPostgresCertificateAuthorityFile,
|
||||
loadPostgresCertificateAuthorityFile,
|
||||
type PostgresCertificateAuthorityFileErrorCode,
|
||||
type PostgresCertificateAuthorityFileInspection,
|
||||
} from '../connection/certificateAuthority';
|
||||
|
||||
export {
|
||||
POSTGRES_SCHEMA_READINESS_ERROR_CODES,
|
||||
PostgresSchemaReadinessError,
|
||||
assertPostgresWorkerCredentialExecutorSchemaReady,
|
||||
type PostgresSchemaReadinessErrorCode,
|
||||
type PostgresSchemaReadinessReport,
|
||||
} from '../schema/schemaReadiness';
|
||||
|
||||
export { postgresqlMainMigrationManifest } from '../migration/migrationManifest';
|
||||
export { PostgresApprovalRequestRepository } from '../approved-action/approvalRequestRepository';
|
||||
export { PostgresApprovedActionExecutionRepository } from '../approved-action/approvedActionExecutionRepository';
|
||||
export { PostgresProjectPolicyRepository } from '../security/projectPolicyRepository';
|
||||
export { PostgresWorkerCredentialManagementPlanReader } from './workerCredentialManagementPlanRepository';
|
||||
export { PostgresWorkerCredentialAdministrationRepository } from './workerCredentialAdministrationRepository';
|
||||
export { PostgresRemoteWorkerSecretDeliveryAuthorityRepository } from './remoteWorkerSecretDeliveryRepository';
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
// PostgreSQL Worker Credential management plans are owned by this domain.
|
||||
import type { PostgresPool } from '@qinglong/runtime-core';
|
||||
import {
|
||||
InvalidWorkerCredentialManagementPlanError,
|
||||
WorkerCredentialManagementPlanConflictError,
|
||||
WorkerCredentialManagementPlanUnavailableError,
|
||||
normalizeWorkerCredentialManagementPlan,
|
||||
type CreateWorkerCredentialManagementPlanResult,
|
||||
type WorkerCredentialManagementPlan,
|
||||
type WorkerCredentialManagementPlanRepository,
|
||||
} from '@qinglong/runtime-core/worker-credential-management-plan';
|
||||
|
||||
import {
|
||||
postgresRequiredJsonObject,
|
||||
postgresSqlState,
|
||||
} from '../repository/definitionRepositorySupport';
|
||||
|
||||
type Row = Record<string, unknown>;
|
||||
|
||||
const ACTION_REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;
|
||||
|
||||
function unavailable(
|
||||
cause?: unknown,
|
||||
): WorkerCredentialManagementPlanUnavailableError {
|
||||
return new WorkerCredentialManagementPlanUnavailableError({
|
||||
cause: cause instanceof Error ? cause : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
function mapStorageError(error: unknown): Error {
|
||||
if (
|
||||
error instanceof InvalidWorkerCredentialManagementPlanError ||
|
||||
error instanceof WorkerCredentialManagementPlanConflictError ||
|
||||
error instanceof WorkerCredentialManagementPlanUnavailableError
|
||||
) {
|
||||
return error;
|
||||
}
|
||||
const state = postgresSqlState(error);
|
||||
if (state === '23503' || state === '23505' || state === '23514') {
|
||||
return new WorkerCredentialManagementPlanConflictError(
|
||||
'plan identity or authority target is already bound',
|
||||
);
|
||||
}
|
||||
return unavailable(error);
|
||||
}
|
||||
|
||||
function same(
|
||||
left: Readonly<WorkerCredentialManagementPlan>,
|
||||
right: Readonly<WorkerCredentialManagementPlan>,
|
||||
): boolean {
|
||||
const semanticFields = (value: Readonly<WorkerCredentialManagementPlan>) => ({
|
||||
actionRef: value.actionRef,
|
||||
authorityProjectId: value.authorityProjectId,
|
||||
action: value.action,
|
||||
target: value.target,
|
||||
requestedBy: value.requestedBy,
|
||||
});
|
||||
return JSON.stringify(semanticFields(left)) === JSON.stringify(semanticFields(right));
|
||||
}
|
||||
|
||||
function normalizeRow(row: Row): Readonly<WorkerCredentialManagementPlan> {
|
||||
try {
|
||||
return normalizeWorkerCredentialManagementPlan(
|
||||
postgresRequiredJsonObject(
|
||||
row.planJson,
|
||||
unavailable,
|
||||
) as unknown as WorkerCredentialManagementPlan,
|
||||
);
|
||||
} catch (error) {
|
||||
throw unavailable(error);
|
||||
}
|
||||
}
|
||||
|
||||
function validateActionRef(value: string): string {
|
||||
if (typeof value !== 'string' || !ACTION_REF_PATTERN.test(value)) {
|
||||
throw new InvalidWorkerCredentialManagementPlanError(
|
||||
'actionRef is invalid',
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export class PostgresWorkerCredentialManagementPlanReader {
|
||||
constructor(protected readonly pool: PostgresPool) {
|
||||
if (
|
||||
!pool ||
|
||||
typeof pool !== 'object' ||
|
||||
typeof pool.query !== 'function'
|
||||
) {
|
||||
throw new TypeError(
|
||||
'PostgreSQL Worker credential management plan reader is invalid',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async findByActionRef(
|
||||
actionRef: string,
|
||||
): Promise<Readonly<WorkerCredentialManagementPlan> | null> {
|
||||
validateActionRef(actionRef);
|
||||
try {
|
||||
const result = await this.pool.query<Row>(
|
||||
`SELECT plan_json AS "planJson"
|
||||
FROM "ql3"."worker_credential_management_plans"
|
||||
WHERE action_ref = $1
|
||||
LIMIT 2`,
|
||||
[actionRef],
|
||||
);
|
||||
if (result.rows.length === 0) return null;
|
||||
if (result.rows.length !== 1) throw unavailable();
|
||||
const plan = normalizeRow(result.rows[0]!);
|
||||
if (plan.actionRef !== actionRef) throw unavailable();
|
||||
return plan;
|
||||
} catch (error) {
|
||||
throw mapStorageError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class PostgresWorkerCredentialManagementPlanRepository
|
||||
extends PostgresWorkerCredentialManagementPlanReader
|
||||
implements WorkerCredentialManagementPlanRepository
|
||||
{
|
||||
async create(
|
||||
planValue: Readonly<WorkerCredentialManagementPlan>,
|
||||
): Promise<Readonly<CreateWorkerCredentialManagementPlanResult>> {
|
||||
const plan = normalizeWorkerCredentialManagementPlan(planValue);
|
||||
try {
|
||||
const inserted = await this.pool.query<Row>(
|
||||
`INSERT INTO "ql3"."worker_credential_management_plans" (
|
||||
action_ref, authority_project_id, action, delivery_id, worker_id,
|
||||
credential_id, previous_credential_id,
|
||||
credential_not_before_at_ms, credential_expires_at_ms,
|
||||
deployment_target_digest, deployment_generation,
|
||||
requested_by_type, requested_by_id, planned_at_ms, expires_at_ms,
|
||||
plan_digest, preview_digest, plan_json
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13,
|
||||
$14, $15, $16, $17, $18::jsonb
|
||||
)
|
||||
ON CONFLICT DO NOTHING
|
||||
RETURNING action_ref AS "actionRef"`,
|
||||
[
|
||||
plan.actionRef,
|
||||
plan.authorityProjectId,
|
||||
plan.action,
|
||||
plan.target.deliveryId,
|
||||
plan.target.workerId,
|
||||
plan.target.credentialId,
|
||||
plan.target.previousCredentialId,
|
||||
plan.target.credentialNotBeforeAtMs,
|
||||
plan.target.credentialExpiresAtMs,
|
||||
plan.target.deploymentTargetDigest,
|
||||
plan.target.deploymentGeneration,
|
||||
plan.requestedBy.type,
|
||||
plan.requestedBy.id,
|
||||
plan.plannedAtMs,
|
||||
plan.expiresAtMs,
|
||||
plan.planDigest,
|
||||
plan.previewDigest,
|
||||
JSON.stringify(plan),
|
||||
],
|
||||
);
|
||||
const stored = await this.findByActionRef(plan.actionRef);
|
||||
if (!stored || !same(stored, plan)) {
|
||||
throw new WorkerCredentialManagementPlanConflictError(
|
||||
'plan identity is already bound to another operation',
|
||||
);
|
||||
}
|
||||
return Object.freeze({
|
||||
status:
|
||||
inserted.rows.length === 1
|
||||
? ('created' as const)
|
||||
: ('existing' as const),
|
||||
plan: stored,
|
||||
});
|
||||
} catch (error) {
|
||||
throw mapStorageError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
// PostgreSQL Worker Credential management quota is owned by this domain.
|
||||
import type { PostgresPool, SecuritySubject } from '@qinglong/runtime-core';
|
||||
import {
|
||||
postgresRequiredBoolean,
|
||||
postgresRequiredInteger,
|
||||
} from '../repository/definitionRepositorySupport';
|
||||
|
||||
const OPERATIONS = [
|
||||
'worker-credential.plan',
|
||||
'worker-credential.propose',
|
||||
'worker-credential.decide',
|
||||
'worker-credential.inspect',
|
||||
] as const;
|
||||
type Operation = (typeof OPERATIONS)[number];
|
||||
type Row = Record<string, unknown>;
|
||||
|
||||
export interface PostgresWorkerCredentialManagementQuotaOptions {
|
||||
readonly windowMs?: number;
|
||||
readonly limits?: Partial<Readonly<Record<Operation, number>>>;
|
||||
}
|
||||
|
||||
export interface ConsumePostgresWorkerCredentialManagementQuotaCommand {
|
||||
readonly projectId: string;
|
||||
readonly subject: Readonly<SecuritySubject>;
|
||||
readonly operation: Operation;
|
||||
readonly idempotencyKey: string;
|
||||
}
|
||||
|
||||
const DEFAULT_LIMITS: Readonly<Record<Operation, number>> = Object.freeze({
|
||||
'worker-credential.plan': 30,
|
||||
'worker-credential.propose': 30,
|
||||
'worker-credential.decide': 60,
|
||||
'worker-credential.inspect': 600,
|
||||
});
|
||||
const ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;
|
||||
const PROJECT = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
||||
|
||||
function integer(
|
||||
value: number | undefined,
|
||||
fallback: number,
|
||||
minimum: number,
|
||||
maximum: number,
|
||||
): number {
|
||||
const candidate = value ?? fallback;
|
||||
if (
|
||||
!Number.isSafeInteger(candidate) ||
|
||||
candidate < minimum ||
|
||||
candidate > maximum
|
||||
) {
|
||||
throw new TypeError('PostgreSQL Worker credential quota bound is invalid');
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
export class PostgresWorkerCredentialManagementQuotaRepository {
|
||||
readonly #windowMs: number;
|
||||
readonly #limits: Readonly<Record<Operation, number>>;
|
||||
|
||||
constructor(
|
||||
private readonly pool: PostgresPool,
|
||||
options: PostgresWorkerCredentialManagementQuotaOptions = {},
|
||||
) {
|
||||
if (!pool || typeof pool.query !== 'function') {
|
||||
throw new TypeError('PostgreSQL Worker credential quota pool is invalid');
|
||||
}
|
||||
if (
|
||||
!options ||
|
||||
typeof options !== 'object' ||
|
||||
Array.isArray(options) ||
|
||||
Object.keys(options).some((key) => key !== 'windowMs' && key !== 'limits') ||
|
||||
(options.limits !== undefined &&
|
||||
(!options.limits ||
|
||||
typeof options.limits !== 'object' ||
|
||||
Array.isArray(options.limits) ||
|
||||
Object.keys(options.limits).some(
|
||||
(key) => !OPERATIONS.includes(key as Operation),
|
||||
)))
|
||||
) {
|
||||
throw new TypeError('PostgreSQL Worker credential quota options are invalid');
|
||||
}
|
||||
this.#windowMs = integer(options.windowMs, 60_000, 1_000, 5 * 60_000);
|
||||
this.#limits = Object.freeze(
|
||||
Object.fromEntries(
|
||||
OPERATIONS.map((operation) => [
|
||||
operation,
|
||||
integer(
|
||||
options.limits?.[operation],
|
||||
DEFAULT_LIMITS[operation],
|
||||
1,
|
||||
1_000,
|
||||
),
|
||||
]),
|
||||
) as unknown as Record<Operation, number>,
|
||||
);
|
||||
}
|
||||
|
||||
async consume(command: ConsumePostgresWorkerCredentialManagementQuotaCommand) {
|
||||
if (
|
||||
!command ||
|
||||
typeof command !== 'object' ||
|
||||
Array.isArray(command) ||
|
||||
Object.keys(command).length !== 4 ||
|
||||
Object.keys(command).some(
|
||||
(key) => !['projectId', 'subject', 'operation', 'idempotencyKey'].includes(key),
|
||||
) ||
|
||||
typeof command.projectId !== 'string' ||
|
||||
!PROJECT.test(command.projectId) ||
|
||||
!command.subject ||
|
||||
command.subject.type !== 'user' ||
|
||||
typeof command.subject.id !== 'string' ||
|
||||
command.subject.id.length < 1 ||
|
||||
command.subject.id.length > 255 ||
|
||||
/[\u0000-\u001f\u007f]/.test(command.subject.id) ||
|
||||
!OPERATIONS.includes(command.operation) ||
|
||||
typeof command.idempotencyKey !== 'string' ||
|
||||
!ID.test(command.idempotencyKey)
|
||||
) {
|
||||
throw new TypeError('PostgreSQL Worker credential quota command is invalid');
|
||||
}
|
||||
const limit = this.#limits[command.operation];
|
||||
let result = await this.pool.query<Row>(
|
||||
`
|
||||
WITH database_clock AS (
|
||||
SELECT floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint AS now_ms
|
||||
)
|
||||
INSERT INTO "ql3"."worker_credential_management_quota_buckets" (
|
||||
project_id, subject_type, subject_id, operation,
|
||||
window_started_at_ms, consumed_count, receipt_ids, updated_at_ms
|
||||
)
|
||||
SELECT $1, $2, $3, $4,
|
||||
(now_ms / $6::bigint) * $6::bigint,
|
||||
1, jsonb_build_array($5::text), now_ms
|
||||
FROM database_clock
|
||||
ON CONFLICT (project_id, subject_type, subject_id, operation)
|
||||
DO UPDATE SET
|
||||
window_started_at_ms = CASE WHEN
|
||||
"worker_credential_management_quota_buckets".window_started_at_ms + $6::bigint
|
||||
<= EXCLUDED.updated_at_ms
|
||||
THEN (EXCLUDED.updated_at_ms / $6::bigint) * $6::bigint
|
||||
ELSE "worker_credential_management_quota_buckets".window_started_at_ms END,
|
||||
consumed_count = CASE WHEN
|
||||
"worker_credential_management_quota_buckets".window_started_at_ms + $6::bigint
|
||||
<= EXCLUDED.updated_at_ms THEN 1
|
||||
WHEN "worker_credential_management_quota_buckets".receipt_ids ? $5::text
|
||||
THEN "worker_credential_management_quota_buckets".consumed_count
|
||||
ELSE "worker_credential_management_quota_buckets".consumed_count + 1 END,
|
||||
receipt_ids = CASE WHEN
|
||||
"worker_credential_management_quota_buckets".window_started_at_ms + $6::bigint
|
||||
<= EXCLUDED.updated_at_ms THEN jsonb_build_array($5::text)
|
||||
WHEN "worker_credential_management_quota_buckets".receipt_ids ? $5::text
|
||||
THEN "worker_credential_management_quota_buckets".receipt_ids
|
||||
ELSE "worker_credential_management_quota_buckets".receipt_ids
|
||||
|| jsonb_build_array($5::text) END,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms
|
||||
WHERE
|
||||
"worker_credential_management_quota_buckets".window_started_at_ms + $6::bigint
|
||||
<= EXCLUDED.updated_at_ms
|
||||
OR "worker_credential_management_quota_buckets".receipt_ids ? $5::text
|
||||
OR "worker_credential_management_quota_buckets".consumed_count < $7::integer
|
||||
RETURNING true AS admitted,
|
||||
consumed_count AS "consumedCount",
|
||||
window_started_at_ms + $6::bigint AS "resetAtMs",
|
||||
updated_at_ms AS "observedAtMs"
|
||||
`.trim(),
|
||||
[
|
||||
command.projectId,
|
||||
command.subject.type,
|
||||
command.subject.id,
|
||||
command.operation,
|
||||
command.idempotencyKey,
|
||||
this.#windowMs,
|
||||
limit,
|
||||
],
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
result = await this.pool.query<Row>(
|
||||
`
|
||||
WITH database_clock AS (
|
||||
SELECT floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint AS now_ms
|
||||
)
|
||||
SELECT false AS admitted,
|
||||
bucket.consumed_count AS "consumedCount",
|
||||
bucket.window_started_at_ms + $5::bigint AS "resetAtMs",
|
||||
database_clock.now_ms AS "observedAtMs"
|
||||
FROM "ql3"."worker_credential_management_quota_buckets" AS bucket
|
||||
CROSS JOIN database_clock
|
||||
WHERE bucket.project_id = $1 AND bucket.subject_type = $2
|
||||
AND bucket.subject_id = $3 AND bucket.operation = $4
|
||||
LIMIT 2
|
||||
`.trim(),
|
||||
[
|
||||
command.projectId,
|
||||
command.subject.type,
|
||||
command.subject.id,
|
||||
command.operation,
|
||||
this.#windowMs,
|
||||
],
|
||||
);
|
||||
}
|
||||
if (result.rows.length !== 1) throw new Error('Worker credential quota is unavailable');
|
||||
const row = result.rows[0]!;
|
||||
const admitted = postgresRequiredBoolean(row.admitted, () => new Error());
|
||||
const consumed = postgresRequiredInteger(row.consumedCount, () => new Error());
|
||||
const resetAtMs = postgresRequiredInteger(row.resetAtMs, () => new Error());
|
||||
const observedAtMs = postgresRequiredInteger(row.observedAtMs, () => new Error());
|
||||
if (
|
||||
consumed < 1 ||
|
||||
consumed > limit ||
|
||||
resetAtMs <= observedAtMs ||
|
||||
resetAtMs > observedAtMs + this.#windowMs
|
||||
) {
|
||||
throw new Error('Worker credential quota is unavailable');
|
||||
}
|
||||
return Object.freeze({
|
||||
admitted,
|
||||
retryAfterMs: admitted ? null : Math.max(1, resetAtMs - observedAtMs),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Worker Credential manager composition is owned by this PostgreSQL domain.
|
||||
export {
|
||||
PgPoolBinding,
|
||||
createPostgresDatabaseOpener,
|
||||
isPostgresTlsDnsServername,
|
||||
type OpenPostgresDatabaseOptions,
|
||||
type PostgresConnectionOptions,
|
||||
type PostgresDatabaseRole,
|
||||
type PostgresPoolOptions,
|
||||
type PostgresTlsOptions,
|
||||
type QingLongPostgresClient,
|
||||
type QingLongPostgresDatabaseResource,
|
||||
type QingLongPostgresPool,
|
||||
type QingLongPostgresQueryResult,
|
||||
} from '../connection/pool';
|
||||
|
||||
export {
|
||||
PostgresConnectionEnvironmentError,
|
||||
loadPostgresConnectionEnvironment,
|
||||
type PostgresConnectionEnvironment,
|
||||
type PostgresConnectionEnvironmentKeys,
|
||||
} from '../connection/connectionEnvironment';
|
||||
|
||||
export {
|
||||
POSTGRES_CA_FILE_ERROR_CODES,
|
||||
POSTGRES_CA_MAX_CERTIFICATES,
|
||||
POSTGRES_CA_MAX_FILE_BYTES,
|
||||
PostgresCertificateAuthorityFileError,
|
||||
inspectPostgresCertificateAuthorityFile,
|
||||
loadPostgresCertificateAuthorityFile,
|
||||
type PostgresCertificateAuthorityFileErrorCode,
|
||||
type PostgresCertificateAuthorityFileInspection,
|
||||
} from '../connection/certificateAuthority';
|
||||
|
||||
export {
|
||||
POSTGRES_SCHEMA_READINESS_ERROR_CODES,
|
||||
PostgresSchemaReadinessError,
|
||||
assertPostgresWorkerCredentialManagerSchemaReady,
|
||||
type PostgresSchemaReadinessErrorCode,
|
||||
type PostgresSchemaReadinessReport,
|
||||
} from '../schema/schemaReadiness';
|
||||
|
||||
export { postgresqlMainMigrationManifest } from '../migration/migrationManifest';
|
||||
export { PostgresApprovalRequestRepository } from '../approved-action/approvalRequestRepository';
|
||||
export { PostgresProjectPolicyRepository } from '../security/projectPolicyRepository';
|
||||
export { PostgresWorkerCredentialManagementPlanRepository } from './workerCredentialManagementPlanRepository';
|
||||
export {
|
||||
PostgresPluginPackageIdentityKeysetLedgerRepository as PostgresWorkerCredentialManagementIdentityKeysetLedgerRepository,
|
||||
type ClusterManagementIdentityAuthority,
|
||||
} from '../management/pluginPackageIdentityKeysetLedgerRepository';
|
||||
export {
|
||||
PostgresWorkerCredentialManagementQuotaRepository,
|
||||
type ConsumePostgresWorkerCredentialManagementQuotaCommand,
|
||||
type PostgresWorkerCredentialManagementQuotaOptions,
|
||||
} from './workerCredentialManagementQuotaRepository';
|
||||
@@ -0,0 +1,74 @@
|
||||
// Runtime Worker Credential resolution persistence is owned by this domain.
|
||||
import {
|
||||
WorkerCredentialUnavailableError,
|
||||
normalizeWorkerCredentialId,
|
||||
normalizeWorkerCredentialRecord,
|
||||
type WorkerCredentialRecord,
|
||||
type WorkerCredentialRepository,
|
||||
} from '@qinglong/runtime-core/worker-credential';
|
||||
import type { PostgresPool } from '@qinglong/runtime-core';
|
||||
|
||||
type Row = Record<string, unknown>;
|
||||
|
||||
function text(row: Row, key: string): string {
|
||||
const value = row[key];
|
||||
if (typeof value !== 'string' || value.length < 1) {
|
||||
throw new TypeError(`PostgreSQL Worker credential ${key} is invalid`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function integer(row: Row, key: string): number {
|
||||
const raw = row[key];
|
||||
const value = typeof raw === 'string' && /^\d+$/.test(raw) ? Number(raw) : raw;
|
||||
if (typeof value !== 'number' || !Number.isSafeInteger(value)) {
|
||||
throw new TypeError(`PostgreSQL Worker credential ${key} is invalid`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export class PostgresWorkerCredentialRepository
|
||||
implements WorkerCredentialRepository
|
||||
{
|
||||
constructor(private readonly pool: PostgresPool) {}
|
||||
|
||||
async resolve(requestedCredentialId: string): Promise<WorkerCredentialRecord | null> {
|
||||
const credentialId = normalizeWorkerCredentialId(requestedCredentialId);
|
||||
try {
|
||||
const result = await this.pool.query<Row>(
|
||||
`
|
||||
SELECT credential_id AS "credentialId", version, state,
|
||||
worker_id AS "workerId", secret_digest AS "secretDigest",
|
||||
created_at_ms AS "createdAtMs",
|
||||
not_before_at_ms AS "notBeforeAtMs",
|
||||
expires_at_ms AS "expiresAtMs"
|
||||
FROM "ql3"."worker_credentials"
|
||||
WHERE credential_id = $1
|
||||
ORDER BY version DESC
|
||||
LIMIT 2
|
||||
`,
|
||||
[credentialId],
|
||||
);
|
||||
if (result.rows.length > 1) {
|
||||
const first = integer(result.rows[0]!, 'version');
|
||||
const second = integer(result.rows[1]!, 'version');
|
||||
if (first <= second) throw new TypeError('Worker credential order is invalid');
|
||||
}
|
||||
const row = result.rows[0];
|
||||
if (!row) return null;
|
||||
return normalizeWorkerCredentialRecord({
|
||||
credentialId: text(row, 'credentialId'),
|
||||
version: integer(row, 'version'),
|
||||
state: text(row, 'state') as WorkerCredentialRecord['state'],
|
||||
workerId: text(row, 'workerId'),
|
||||
secretDigest: text(row, 'secretDigest'),
|
||||
createdAtMs: integer(row, 'createdAtMs'),
|
||||
notBeforeAtMs: integer(row, 'notBeforeAtMs'),
|
||||
expiresAtMs: integer(row, 'expiresAtMs'),
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof WorkerCredentialUnavailableError) throw error;
|
||||
throw new WorkerCredentialUnavailableError();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user