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,452 @@
import path from 'node:path';
import {
assertApiCredentialId,
assertProjectPolicyProjectId,
normalizeProjectPolicySubject,
readPrivateLocalCommandFile,
type SecuritySubject,
} from './codecAuthority';
import {
LocalIdentityCredentialCommandConfigurationError,
type BaseInspectionRequest,
type BaseMutationRequest,
type BaseTargetMutationRequest,
type LocalIdentityCredentialCommand,
type LocalIdentityCredentialCommandOptions,
} from './contracts';
export const MAX_PATH_BYTES = 4096;
export const MAX_VERSION = 2_147_483_647;
export const MIN_CREDENTIAL_LIFETIME_MS = 60_000;
export const MAX_CREDENTIAL_LIFETIME_MS = 2 * 365 * 24 * 60 * 60 * 1000;
export const UUID_V4_PATTERN =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
export const REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
export const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
export 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 LocalIdentityCredentialCommandConfigurationError(
`${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 LocalIdentityCredentialCommandConfigurationError(
`${label} shape is invalid`,
);
}
}
export function boundedPath(value: unknown, label: 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 LocalIdentityCredentialCommandConfigurationError(
`${label} must be a normalized bounded absolute non-root path`,
);
}
return value;
}
export function descendant(
root: string,
candidate: string,
label: string,
): void {
const relative = path.relative(root, candidate);
if (
relative.length === 0 ||
relative === '..' ||
relative.startsWith(`..${path.sep}`) ||
path.isAbsolute(relative)
) {
throw new LocalIdentityCredentialCommandConfigurationError(
`${label} must be a descendant of deploymentRoot`,
);
}
}
export function requiresDelivery(
operation: LocalIdentityCredentialCommand['operation'],
) {
return (
operation === 'credential.issue' ||
operation === 'credential.rotate' ||
operation === 'credential.delivery.acknowledge'
);
}
export function normalizeOptions(
value: unknown,
operation: LocalIdentityCredentialCommand['operation'],
): Readonly<LocalIdentityCredentialCommandOptions> {
const hasBusyTimeout =
!!value &&
typeof value === 'object' &&
!Array.isArray(value) &&
Object.hasOwn(value, 'busyTimeoutMs');
const deliveryRequired = requiresDelivery(operation);
exactObject(
value,
[
'deploymentRoot',
'databasePath',
'profile',
'ownerPepperKeyringDirectory',
'credentialFilePath',
...(deliveryRequired ? ['credentialDeliveryDirectory'] : []),
...(hasBusyTimeout ? ['busyTimeoutMs'] : []),
],
'options',
);
const deploymentRoot = boundedPath(value.deploymentRoot, 'deploymentRoot');
const databasePath = boundedPath(value.databasePath, 'databasePath');
const ownerPepperKeyringDirectory = boundedPath(
value.ownerPepperKeyringDirectory,
'ownerPepperKeyringDirectory',
);
const credentialFilePath = boundedPath(
value.credentialFilePath,
'credentialFilePath',
);
for (const [label, candidate] of [
['databasePath', databasePath],
['ownerPepperKeyringDirectory', ownerPepperKeyringDirectory],
['credentialFilePath', credentialFilePath],
] as const) {
descendant(deploymentRoot, candidate, label);
}
let credentialDeliveryDirectory: string | undefined;
if (deliveryRequired) {
credentialDeliveryDirectory = boundedPath(
value.credentialDeliveryDirectory,
'credentialDeliveryDirectory',
);
descendant(
deploymentRoot,
credentialDeliveryDirectory,
'credentialDeliveryDirectory',
);
if (
credentialDeliveryDirectory === path.dirname(databasePath) ||
credentialDeliveryDirectory === ownerPepperKeyringDirectory
) {
throw new LocalIdentityCredentialCommandConfigurationError(
'credentialDeliveryDirectory must not share database or keyring storage',
);
}
}
if (value.profile !== 'edge' && value.profile !== 'standalone') {
throw new LocalIdentityCredentialCommandConfigurationError(
'profile must be edge or standalone',
);
}
if (
value.busyTimeoutMs !== undefined &&
(!Number.isSafeInteger(value.busyTimeoutMs) ||
(value.busyTimeoutMs as number) < 100 ||
(value.busyTimeoutMs as number) > 30_000)
) {
throw new LocalIdentityCredentialCommandConfigurationError(
'busyTimeoutMs is invalid',
);
}
return Object.freeze({
deploymentRoot,
databasePath,
profile: value.profile,
ownerPepperKeyringDirectory,
credentialFilePath,
...(credentialDeliveryDirectory === undefined
? {}
: { credentialDeliveryDirectory }),
...(value.busyTimeoutMs === undefined
? {}
: { busyTimeoutMs: value.busyTimeoutMs as number }),
});
}
export function normalizeCommonRequest(
value: Record<string, unknown>,
): Readonly<BaseMutationRequest> {
try {
assertProjectPolicyProjectId(value.projectId as string);
} catch (error) {
throw new LocalIdentityCredentialCommandConfigurationError(
'projectId is invalid',
error,
);
}
if (
typeof value.mutationId !== 'string' ||
!UUID_V4_PATTERN.test(value.mutationId) ||
typeof value.failureAuditEventId !== 'string' ||
!UUID_V4_PATTERN.test(value.failureAuditEventId) ||
value.failureAuditEventId === value.mutationId ||
typeof value.requestId !== 'string' ||
!REQUEST_ID_PATTERN.test(value.requestId)
) {
throw new LocalIdentityCredentialCommandConfigurationError(
'mutation or request identity is invalid',
);
}
return Object.freeze({
projectId: value.projectId as string,
mutationId: value.mutationId,
requestId: value.requestId,
failureAuditEventId: value.failureAuditEventId,
});
}
export function normalizeInspectionCommonRequest(
value: Record<string, unknown>,
): Readonly<BaseInspectionRequest> {
try {
assertProjectPolicyProjectId(value.projectId as string);
} catch (error) {
throw new LocalIdentityCredentialCommandConfigurationError(
'projectId is invalid',
error,
);
}
if (
typeof value.auditEventId !== 'string' ||
!UUID_V4_PATTERN.test(value.auditEventId) ||
typeof value.requestId !== 'string' ||
!REQUEST_ID_PATTERN.test(value.requestId)
) {
throw new LocalIdentityCredentialCommandConfigurationError(
'audit or request identity is invalid',
);
}
return Object.freeze({
projectId: value.projectId as string,
requestId: value.requestId,
auditEventId: value.auditEventId,
});
}
export function normalizeInspectionTarget(
value: unknown,
): Readonly<SecuritySubject> {
let target: Readonly<SecuritySubject>;
try {
target = normalizeProjectPolicySubject(value as SecuritySubject);
} catch (error) {
throw new LocalIdentityCredentialCommandConfigurationError(
'target is invalid',
error,
);
}
if (!['user', 'api_app', 'mcp_client', 'agent'].includes(target.type)) {
throw new LocalIdentityCredentialCommandConfigurationError(
'target is invalid',
);
}
return target;
}
export function normalizeTargetRequest(
value: Record<string, unknown>,
): Readonly<BaseTargetMutationRequest> {
const common = normalizeCommonRequest(value);
let target: Readonly<SecuritySubject>;
try {
target = normalizeProjectPolicySubject(value.target as SecuritySubject);
} catch (error) {
throw new LocalIdentityCredentialCommandConfigurationError(
'target is invalid',
error,
);
}
if (
!['user', 'api_app', 'mcp_client', 'agent'].includes(target.type) ||
!Number.isSafeInteger(value.expectedCurrentVersion) ||
(value.expectedCurrentVersion as number) < 0 ||
(value.expectedCurrentVersion as number) >= MAX_VERSION
) {
throw new LocalIdentityCredentialCommandConfigurationError(
'target or expectedCurrentVersion is invalid',
);
}
return Object.freeze({
...common,
target,
expectedCurrentVersion: value.expectedCurrentVersion as number,
});
}
export function normalizeRequest(
value: unknown,
operation: LocalIdentityCredentialCommand['operation'],
): LocalIdentityCredentialCommand['request'] {
const inspection =
operation === 'identity.inspect' || operation === 'credential.inspect';
if (inspection) {
const identityInspection = operation === 'identity.inspect';
exactObject(
value,
[
'projectId',
...(identityInspection ? ['target'] : ['credentialId']),
'requestId',
'auditEventId',
],
'request',
);
const common = normalizeInspectionCommonRequest(value);
if (identityInspection) {
return Object.freeze({
...common,
target: normalizeInspectionTarget(value.target),
});
}
try {
assertApiCredentialId(value.credentialId as string);
} catch (error) {
throw new LocalIdentityCredentialCommandConfigurationError(
'credentialId is invalid',
error,
);
}
return Object.freeze({
...common,
credentialId: value.credentialId as string,
});
}
const identity = operation.startsWith('identity.');
const activeCredential =
operation === 'credential.issue' || operation === 'credential.rotate';
const revokeCredential = operation === 'credential.revoke';
const acknowledge = operation === 'credential.delivery.acknowledge';
exactObject(
value,
[
'projectId',
...(acknowledge ? [] : ['target', 'expectedCurrentVersion']),
...(activeCredential || revokeCredential ? ['credentialId'] : []),
...(activeCredential ? ['lifetimeMs'] : []),
...(acknowledge
? ['credentialMutationId', 'expectedDeliveryDigest']
: []),
'mutationId',
'requestId',
'failureAuditEventId',
],
'request',
);
if (acknowledge) {
const common = normalizeCommonRequest(value);
if (
typeof value.credentialMutationId !== 'string' ||
!UUID_V4_PATTERN.test(value.credentialMutationId) ||
value.credentialMutationId === common.mutationId ||
typeof value.expectedDeliveryDigest !== 'string' ||
!DIGEST_PATTERN.test(value.expectedDeliveryDigest)
) {
throw new LocalIdentityCredentialCommandConfigurationError(
'delivery acknowledgement is invalid',
);
}
return Object.freeze({
...common,
credentialMutationId: value.credentialMutationId,
expectedDeliveryDigest: value.expectedDeliveryDigest,
});
}
const target = normalizeTargetRequest(value);
if (identity) return target;
try {
assertApiCredentialId(value.credentialId as string);
} catch (error) {
throw new LocalIdentityCredentialCommandConfigurationError(
'credentialId is invalid',
error,
);
}
if (
activeCredential &&
(!Number.isSafeInteger(value.lifetimeMs) ||
(value.lifetimeMs as number) < MIN_CREDENTIAL_LIFETIME_MS ||
(value.lifetimeMs as number) > MAX_CREDENTIAL_LIFETIME_MS)
) {
throw new LocalIdentityCredentialCommandConfigurationError(
'lifetimeMs is invalid',
);
}
return Object.freeze({
...target,
credentialId: value.credentialId as string,
...(activeCredential ? { lifetimeMs: value.lifetimeMs as number } : {}),
});
}
export function normalizeCommand(
value: unknown,
): Readonly<LocalIdentityCredentialCommand> {
exactObject(
value,
['schemaVersion', 'operation', 'options', 'request'],
'command',
);
const operations: readonly LocalIdentityCredentialCommand['operation'][] = [
'identity.inspect',
'identity.register',
'identity.enable',
'identity.disable',
'credential.inspect',
'credential.issue',
'credential.rotate',
'credential.revoke',
'credential.delivery.acknowledge',
];
if (
value.schemaVersion !== 1 ||
typeof value.operation !== 'string' ||
!operations.includes(
value.operation as LocalIdentityCredentialCommand['operation'],
)
) {
throw new LocalIdentityCredentialCommandConfigurationError(
'command version or operation is invalid',
);
}
const operation =
value.operation as LocalIdentityCredentialCommand['operation'];
return Object.freeze({
schemaVersion: 1,
operation,
options: normalizeOptions(value.options, operation),
request: normalizeRequest(value.request, operation),
} as LocalIdentityCredentialCommand);
}
export function readCommandFile(
candidatePath: string,
): Readonly<LocalIdentityCredentialCommand> {
try {
return normalizeCommand(readPrivateLocalCommandFile(candidatePath));
} catch (error) {
if (error instanceof LocalIdentityCredentialCommandConfigurationError) {
throw error;
}
throw new LocalIdentityCredentialCommandConfigurationError(
'command file cannot be read',
error,
);
}
}
@@ -0,0 +1,7 @@
export { readPrivateLocalCommandFile } from '@qinglong/local-command-file';
export { assertApiCredentialId } from '@qinglong/runtime-core/api-credential';
export {
assertProjectPolicyProjectId,
normalizeProjectPolicySubject,
} from '@qinglong/runtime-core/project-policy';
export type { SecuritySubject } from '@qinglong/runtime-core/security';
@@ -0,0 +1,9 @@
export type { createLocalIdentityCredentialAdministrationService } from '@qinglong/local-admin/identity-credential-administration';
export type { establishAuthenticatedLocalCommand } from '@qinglong/local-owner-console/authenticated-command';
export type {
LocalCredentialAdministrationDeliveryRecord,
LocalCredentialAdministrationDeliverySummary,
} from '@qinglong/local-owner-console/credential-administration-delivery';
export type { LocalOwnerPepperKeyringFileProvider } from '@qinglong/local-owner-console/pepper-custody';
export type { openLocalSqliteIdentityCredentialAdministrationDatabase } from '@qinglong/local-sqlite/identity-credential-administration';
export type { SecuritySubject } from '@qinglong/runtime-core/security';
@@ -0,0 +1,252 @@
import type {
createLocalIdentityCredentialAdministrationService,
establishAuthenticatedLocalCommand,
LocalCredentialAdministrationDeliveryRecord,
LocalCredentialAdministrationDeliverySummary,
LocalOwnerPepperKeyringFileProvider,
openLocalSqliteIdentityCredentialAdministrationDatabase,
SecuritySubject,
} from './contractAuthority';
export type IdentityCommandOperation =
| 'identity.register'
| 'identity.enable'
| 'identity.disable';
export type CredentialCommandOperation =
| 'credential.issue'
| 'credential.rotate'
| 'credential.revoke';
export type DeliveryCommandOperation = 'credential.delivery.acknowledge';
export type InspectionCommandOperation =
| 'identity.inspect'
| 'credential.inspect';
export interface LocalIdentityCredentialCommandOptions {
readonly deploymentRoot: string;
readonly databasePath: string;
readonly profile: 'edge' | 'standalone';
readonly ownerPepperKeyringDirectory: string;
readonly credentialFilePath: string;
readonly credentialDeliveryDirectory?: string;
readonly busyTimeoutMs?: number;
}
export interface BaseMutationRequest {
readonly projectId: string;
readonly mutationId: string;
readonly requestId: string;
readonly failureAuditEventId: string;
}
export interface BaseTargetMutationRequest extends BaseMutationRequest {
readonly target: SecuritySubject;
readonly expectedCurrentVersion: number;
}
export interface BaseInspectionRequest {
readonly projectId: string;
readonly requestId: string;
readonly auditEventId: string;
}
export interface LocalIdentityInspectionCommand {
readonly schemaVersion: 1;
readonly operation: 'identity.inspect';
readonly options: LocalIdentityCredentialCommandOptions;
readonly request: BaseInspectionRequest & {
readonly target: SecuritySubject;
};
}
export interface LocalApiCredentialInspectionCommand {
readonly schemaVersion: 1;
readonly operation: 'credential.inspect';
readonly options: LocalIdentityCredentialCommandOptions;
readonly request: BaseInspectionRequest & {
readonly credentialId: string;
};
}
export interface LocalIdentityAdministrationCommand {
readonly schemaVersion: 1;
readonly operation: IdentityCommandOperation;
readonly options: LocalIdentityCredentialCommandOptions;
readonly request: BaseTargetMutationRequest;
}
export interface LocalApiCredentialIssueCommand {
readonly schemaVersion: 1;
readonly operation: 'credential.issue' | 'credential.rotate';
readonly options: LocalIdentityCredentialCommandOptions & {
readonly credentialDeliveryDirectory: string;
};
readonly request: BaseTargetMutationRequest & {
readonly credentialId: string;
readonly lifetimeMs: number;
};
}
export interface LocalApiCredentialRevokeCommand {
readonly schemaVersion: 1;
readonly operation: 'credential.revoke';
readonly options: LocalIdentityCredentialCommandOptions;
readonly request: BaseTargetMutationRequest & {
readonly credentialId: string;
};
}
export interface LocalCredentialDeliveryAcknowledgeCommand {
readonly schemaVersion: 1;
readonly operation: DeliveryCommandOperation;
readonly options: LocalIdentityCredentialCommandOptions & {
readonly credentialDeliveryDirectory: string;
};
readonly request: BaseMutationRequest & {
readonly credentialMutationId: string;
readonly expectedDeliveryDigest: string;
};
}
export type LocalIdentityCredentialCommand =
| LocalIdentityInspectionCommand
| LocalApiCredentialInspectionCommand
| LocalIdentityAdministrationCommand
| LocalApiCredentialIssueCommand
| LocalApiCredentialRevokeCommand
| LocalCredentialDeliveryAcknowledgeCommand;
export type LocalIdentityCredentialCommandResult =
| Readonly<{
schemaVersion: 1;
operation: InspectionCommandOperation;
projectId: string;
found: false;
}>
| Readonly<{
schemaVersion: 1;
operation: 'identity.inspect';
projectId: string;
found: true;
target: Readonly<SecuritySubject>;
version: number;
identityStatus: 'active' | 'disabled';
createdAtMs: number;
updatedAtMs: number;
}>
| Readonly<{
schemaVersion: 1;
operation: 'credential.inspect';
projectId: string;
found: true;
credentialId: string;
target: Readonly<SecuritySubject>;
version: number;
state: 'active' | 'revoked';
subjectStatus: 'active' | 'disabled';
createdAtMs: number;
notBeforeAtMs: number;
expiresAtMs: number;
}>
| Readonly<{
schemaVersion: 1;
operation: IdentityCommandOperation;
status: 'inserted' | 'existing';
projectId: string;
target: Readonly<SecuritySubject>;
version: number;
identityStatus: 'active' | 'disabled';
}>
| Readonly<{
schemaVersion: 1;
operation: CredentialCommandOperation;
status: 'inserted' | 'existing';
projectId: string;
target: Readonly<SecuritySubject>;
credentialId: string;
version: number;
state: 'active' | 'revoked';
expiresAtMs: number;
delivery?: Readonly<{
fileName: string;
digest: string;
}>;
}>
| Readonly<{
schemaVersion: 1;
operation: DeliveryCommandOperation;
status: 'inserted' | 'existing';
projectId: string;
credentialMutationId: string;
acknowledgementMutationId: string;
deliveryDigest: string;
cleanup: 'removed' | 'absent';
}>;
export interface CredentialDelivery {
readonly directory: string;
prepare(
record: LocalCredentialAdministrationDeliveryRecord,
): Readonly<LocalCredentialAdministrationDeliveryRecord>;
digest(record: LocalCredentialAdministrationDeliveryRecord): string;
publish(
record: LocalCredentialAdministrationDeliveryRecord,
expectedDeliveryDigest: string,
): Readonly<LocalCredentialAdministrationDeliverySummary>;
removeAcknowledged(
mutationId: string,
expectedDeliveryDigest: string,
): 'removed' | 'absent';
}
export interface PepperMaterialProvider {
resolve(
pepperKeyId: string,
): ReturnType<LocalOwnerPepperKeyringFileProvider['resolve']>;
}
export interface LocalIdentityCredentialCommandRunner {
run(
commandFilePath: string,
): Promise<Readonly<LocalIdentityCredentialCommandResult>>;
}
export interface LocalIdentityCredentialCommandRunnerDependencies {
readonly openDatabase: typeof openLocalSqliteIdentityCredentialAdministrationDatabase;
readonly authenticate: typeof establishAuthenticatedLocalCommand;
readonly createService: typeof createLocalIdentityCredentialAdministrationService;
readonly createDelivery: (directory: string) => CredentialDelivery;
readonly createPepperProvider: (directory: string) => PepperMaterialProvider;
readonly randomBytes: (size: number) => Buffer;
readonly now: () => number;
}
export class LocalIdentityCredentialCommandConfigurationError extends TypeError {
readonly code = 'LOCAL_IDENTITY_CREDENTIAL_COMMAND_CONFIGURATION_INVALID';
constructor(message: string, readonly cause?: unknown) {
super(`Local Identity credential command is invalid: ${message}`);
this.name = 'LocalIdentityCredentialCommandConfigurationError';
}
}
export class LocalIdentityCredentialCommandPepperUnavailableError extends Error {
readonly code = 'LOCAL_IDENTITY_CREDENTIAL_COMMAND_PEPPER_UNAVAILABLE';
constructor() {
super('Local Identity credential active pepper material is unavailable');
this.name = 'LocalIdentityCredentialCommandPepperUnavailableError';
}
}
export class LocalIdentityCredentialCommandCurrentCredentialError extends Error {
readonly code =
'LOCAL_IDENTITY_CREDENTIAL_COMMAND_CURRENT_CREDENTIAL_INVALID';
constructor() {
super('Local Identity credential current version is unavailable');
this.name = 'LocalIdentityCredentialCommandCurrentCredentialError';
}
}
@@ -0,0 +1,45 @@
export {
LocalIdentityCredentialAdministrationAuthenticationError,
LocalIdentityCredentialAdministrationAuthorizationError,
LocalIdentityCredentialAdministrationServiceUnavailableError,
createLocalIdentityCredentialAdministrationService,
} from '@qinglong/local-admin/identity-credential-administration';
export type { LocalIdentityCredentialAdministrationService } from '@qinglong/local-admin/identity-credential-administration';
export {
AuthenticatedLocalCommandAuthenticationError,
establishAuthenticatedLocalCommand,
} from '@qinglong/local-owner-console/authenticated-command';
export type { AuthenticatedLocalCommand } from '@qinglong/local-owner-console/authenticated-command';
export { FileLocalCredentialAdministrationDelivery } from '@qinglong/local-owner-console/credential-administration-delivery';
export type {
LocalCredentialAdministrationDeliveryRecord,
LocalCredentialAdministrationDeliverySummary,
} from '@qinglong/local-owner-console/credential-administration-delivery';
export { LocalOwnerPepperKeyringFileProvider } from '@qinglong/local-owner-console/pepper-custody';
export { LocalSqliteAuthenticatedManagementFenceError } from '@qinglong/local-sqlite/authenticated-management';
export type { LocalSqliteAuthenticatedUserCredentialFence } from '@qinglong/local-sqlite/authenticated-management';
export { openLocalSqliteIdentityCredentialAdministrationDatabase } from '@qinglong/local-sqlite/identity-credential-administration';
export type { LocalSqliteIdentityCredentialAdministrationDatabase } from '@qinglong/local-sqlite/identity-credential-administration';
export {
ApiCredentialAdministrationMutationConflictError,
ApiCredentialAdministrationSubjectNotFoundError,
ApiCredentialAdministrationVersionConflictError,
} from '@qinglong/runtime-core/api-credential-administration';
export type { ApiCredentialRecord } from '@qinglong/runtime-core/api-credential';
export {
API_CREDENTIAL_SECRET_BYTES,
apiCredentialSecretDigest,
} from '@qinglong/runtime-core/api-credential-token';
export {
IdentityAdministrationMutationConflictError,
IdentityAdministrationVersionConflictError,
} from '@qinglong/runtime-core/identity-administration';
export {
LocalCredentialDeliveryMutationConflictError,
LocalCredentialOwnerContinuityError,
LocalIdentityCredentialAdministrationUnavailableError,
LocalIdentityCredentialAuthorizationFenceConflictError,
LocalIdentityOwnerBindingConflictError,
} from '@qinglong/runtime-core/local-identity-credential-administration';
export type { SecuritySubject } from '@qinglong/runtime-core/security';
export type { SecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
@@ -0,0 +1,198 @@
import path from 'node:path';
import {
ApiCredentialAdministrationMutationConflictError,
ApiCredentialAdministrationSubjectNotFoundError,
ApiCredentialAdministrationVersionConflictError,
AuthenticatedLocalCommandAuthenticationError,
IdentityAdministrationMutationConflictError,
IdentityAdministrationVersionConflictError,
LocalCredentialDeliveryMutationConflictError,
LocalCredentialOwnerContinuityError,
LocalIdentityCredentialAdministrationAuthenticationError,
LocalIdentityCredentialAdministrationAuthorizationError,
LocalIdentityCredentialAdministrationServiceUnavailableError,
LocalIdentityCredentialAdministrationUnavailableError,
LocalIdentityCredentialAuthorizationFenceConflictError,
LocalIdentityOwnerBindingConflictError,
LocalSqliteAuthenticatedManagementFenceError,
type AuthenticatedLocalCommand,
type LocalCredentialAdministrationDeliverySummary,
type LocalIdentityCredentialAdministrationService,
type LocalSqliteAuthenticatedUserCredentialFence,
type LocalSqliteIdentityCredentialAdministrationDatabase,
type SecurityAuditRecord,
type SecuritySubject,
} from './executionAuthority';
import {
LocalIdentityCredentialCommandConfigurationError,
LocalIdentityCredentialCommandCurrentCredentialError,
type LocalApiCredentialIssueCommand,
type LocalIdentityCredentialCommand,
type LocalIdentityCredentialCommandResult,
type LocalIdentityCredentialCommandRunnerDependencies,
} from './contracts';
export function dependencies(
value: LocalIdentityCredentialCommandRunnerDependencies,
): Readonly<LocalIdentityCredentialCommandRunnerDependencies> {
const expected = [
'openDatabase',
'authenticate',
'createService',
'createDelivery',
'createPepperProvider',
'randomBytes',
'now',
];
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.keys(value).sort().join('\0') !== expected.sort().join('\0') ||
expected.some(
(key) =>
typeof value[
key as keyof LocalIdentityCredentialCommandRunnerDependencies
] !== 'function',
)
) {
throw new LocalIdentityCredentialCommandConfigurationError(
'runner dependencies are invalid',
);
}
return Object.freeze({ ...value });
}
export function clock(now: () => number): number {
const value = now();
if (!Number.isSafeInteger(value) || value < 0) {
throw new LocalIdentityCredentialCommandConfigurationError(
'clock is invalid',
);
}
return value;
}
export function failureAudit(
command: Readonly<LocalIdentityCredentialCommand>,
authenticated: Readonly<AuthenticatedLocalCommand> | undefined,
error: unknown,
occurredAtMs: number,
): Readonly<SecurityAuditRecord> | null {
if (
error instanceof LocalIdentityCredentialAdministrationAuthenticationError ||
error instanceof LocalIdentityCredentialAdministrationAuthorizationError ||
error instanceof
LocalIdentityCredentialAdministrationServiceUnavailableError
) {
return null;
}
let outcome: SecurityAuditRecord['outcome'];
let reason: string;
if (
!authenticated ||
error instanceof AuthenticatedLocalCommandAuthenticationError
) {
outcome = 'authentication_rejected';
reason = 'credential_rejected';
} else if (
error instanceof LocalSqliteAuthenticatedManagementFenceError ||
error instanceof LocalIdentityCredentialAuthorizationFenceConflictError
) {
outcome = 'denied';
reason = 'credential_or_policy_fence_rejected';
} else if (
error instanceof LocalIdentityOwnerBindingConflictError ||
error instanceof LocalCredentialOwnerContinuityError
) {
outcome = 'denied';
reason = 'owner_continuity_required';
} else if (
error instanceof IdentityAdministrationVersionConflictError ||
error instanceof ApiCredentialAdministrationVersionConflictError ||
error instanceof ApiCredentialAdministrationSubjectNotFoundError ||
error instanceof LocalIdentityCredentialCommandCurrentCredentialError
) {
outcome = 'denied';
reason = 'current_version_or_subject_conflict';
} else if (
error instanceof IdentityAdministrationMutationConflictError ||
error instanceof ApiCredentialAdministrationMutationConflictError ||
error instanceof LocalCredentialDeliveryMutationConflictError
) {
outcome = 'denied';
reason = 'mutation_conflict';
} else {
return null;
}
return Object.freeze({
eventId:
'failureAuditEventId' in command.request
? command.request.failureAuditEventId
: command.request.auditEventId,
requestId: command.request.requestId,
operationId: command.operation,
projectId: command.request.projectId,
subject: authenticated?.principal.subject ?? null,
authenticationId: authenticated?.principal.authenticationId ?? null,
outcome,
reasons: Object.freeze([reason]),
fence: null,
occurredAtMs,
});
}
export async function activateFence(
database: LocalSqliteIdentityCredentialAdministrationDatabase,
authenticated: Readonly<AuthenticatedLocalCommand>,
): Promise<void> {
await authenticated.confirm();
database.activateUserCredentialFence(
authenticated.databaseFence as Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
);
}
export function sameSubject(
left: Readonly<SecuritySubject>,
right: Readonly<SecuritySubject>,
): boolean {
return left.type === right.type && left.id === right.id;
}
export function activeCredentialResult(
command: Readonly<LocalApiCredentialIssueCommand>,
result: Awaited<
ReturnType<LocalIdentityCredentialAdministrationService['changeCredential']>
>,
delivery: Readonly<LocalCredentialAdministrationDeliverySummary>,
): Readonly<LocalIdentityCredentialCommandResult> {
if (
result.credential.credentialId !== command.request.credentialId ||
result.credential.version !== command.request.expectedCurrentVersion + 1 ||
!sameSubject(result.credential.subject, command.request.target) ||
result.credential.state !== 'active' ||
result.delivery?.digest !== delivery.deliveryDigest ||
delivery.mutationId !== command.request.mutationId ||
delivery.credentialId !== command.request.credentialId ||
delivery.projectId !== command.request.projectId ||
!sameSubject(delivery.subject, command.request.target) ||
path.dirname(delivery.path) !== command.options.credentialDeliveryDirectory
) {
throw new LocalIdentityCredentialAdministrationUnavailableError();
}
return Object.freeze({
schemaVersion: 1 as const,
operation: command.operation,
status: result.status,
projectId: command.request.projectId,
target: result.credential.subject,
credentialId: result.credential.credentialId,
version: result.credential.version,
state: result.credential.state,
expiresAtMs: result.credential.expiresAtMs,
delivery: Object.freeze({
fileName: path.basename(delivery.path),
digest: delivery.deliveryDigest,
}),
});
}
@@ -0,0 +1,369 @@
import { randomBytes as cryptoRandomBytes } from 'node:crypto';
import {
API_CREDENTIAL_SECRET_BYTES,
FileLocalCredentialAdministrationDelivery,
LocalIdentityCredentialAdministrationUnavailableError,
LocalOwnerPepperKeyringFileProvider,
apiCredentialSecretDigest,
createLocalIdentityCredentialAdministrationService,
establishAuthenticatedLocalCommand,
openLocalSqliteIdentityCredentialAdministrationDatabase,
type ApiCredentialRecord,
type AuthenticatedLocalCommand,
type LocalCredentialAdministrationDeliveryRecord,
} from './executionAuthority';
import {
LocalIdentityCredentialCommandConfigurationError,
LocalIdentityCredentialCommandCurrentCredentialError,
LocalIdentityCredentialCommandPepperUnavailableError,
type LocalApiCredentialInspectionCommand,
type LocalApiCredentialIssueCommand,
type LocalApiCredentialRevokeCommand,
type LocalCredentialDeliveryAcknowledgeCommand,
type LocalIdentityAdministrationCommand,
type LocalIdentityCredentialCommandResult,
type LocalIdentityCredentialCommandRunner,
type LocalIdentityCredentialCommandRunnerDependencies,
type LocalIdentityInspectionCommand,
} from './contracts';
import { readCommandFile } from './codec';
import {
activateFence,
activeCredentialResult,
clock,
dependencies,
failureAudit,
sameSubject,
} from './executionSupport';
export function createLocalIdentityCredentialCommandRunner(
candidateDependencies: LocalIdentityCredentialCommandRunnerDependencies = {
openDatabase: openLocalSqliteIdentityCredentialAdministrationDatabase,
authenticate: establishAuthenticatedLocalCommand,
createService: createLocalIdentityCredentialAdministrationService,
createDelivery: (directory) =>
new FileLocalCredentialAdministrationDelivery(directory),
createPepperProvider: (directory) =>
new LocalOwnerPepperKeyringFileProvider(directory),
randomBytes: cryptoRandomBytes,
now: Date.now,
},
): LocalIdentityCredentialCommandRunner {
const adapters = dependencies(candidateDependencies);
return Object.freeze({
async run(commandFilePath: string) {
const command = readCommandFile(commandFilePath);
const database = await adapters.openDatabase({
databasePath: command.options.databasePath,
profile: command.options.profile,
...(command.options.busyTimeoutMs === undefined
? {}
: { busyTimeoutMs: command.options.busyTimeoutMs }),
});
let authenticated: Readonly<AuthenticatedLocalCommand> | undefined;
try {
try {
authenticated = await adapters.authenticate(database, {
deploymentRoot: command.options.deploymentRoot,
databasePath: command.options.databasePath,
ownerPepperKeyringDirectory:
command.options.ownerPepperKeyringDirectory,
credentialFilePath: command.options.credentialFilePath,
authenticationNamespace: 'local_identity_admin',
});
await activateFence(database, authenticated);
const commandNowMs = clock(adapters.now);
const service = adapters.createService(
database.projectPolicy,
database.identityCredentialAdministration,
{ now: () => commandNowMs },
);
if (command.operation === 'identity.inspect') {
const inspectCommand =
command as Readonly<LocalIdentityInspectionCommand>;
const result = await service.inspectIdentity({
projectId: inspectCommand.request.projectId,
target: inspectCommand.request.target,
auditEventId: inspectCommand.request.auditEventId,
requestId: inspectCommand.request.requestId,
principal: authenticated.principal,
});
if (!result.identity) {
return Object.freeze({
schemaVersion: 1 as const,
operation: inspectCommand.operation,
projectId: inspectCommand.request.projectId,
found: false as const,
});
}
return Object.freeze({
schemaVersion: 1 as const,
operation: inspectCommand.operation,
projectId: inspectCommand.request.projectId,
found: true as const,
target: result.identity.subject,
version: result.identity.version,
identityStatus: result.identity.status,
createdAtMs: result.identity.createdAtMs,
updatedAtMs: result.identity.updatedAtMs,
});
}
if (command.operation === 'credential.inspect') {
const inspectCommand =
command as Readonly<LocalApiCredentialInspectionCommand>;
const result = await service.inspectCredential({
projectId: inspectCommand.request.projectId,
credentialId: inspectCommand.request.credentialId,
auditEventId: inspectCommand.request.auditEventId,
requestId: inspectCommand.request.requestId,
principal: authenticated.principal,
});
if (!result.credential) {
return Object.freeze({
schemaVersion: 1 as const,
operation: inspectCommand.operation,
projectId: inspectCommand.request.projectId,
found: false as const,
});
}
return Object.freeze({
schemaVersion: 1 as const,
operation: inspectCommand.operation,
projectId: inspectCommand.request.projectId,
found: true as const,
credentialId: result.credential.credentialId,
target: result.credential.subject,
version: result.credential.version,
state: result.credential.state,
subjectStatus: result.credential.subjectStatus,
createdAtMs: result.credential.createdAtMs,
notBeforeAtMs: result.credential.notBeforeAtMs,
expiresAtMs: result.credential.expiresAtMs,
});
}
if (command.operation.startsWith('identity.')) {
const identityCommand =
command as Readonly<LocalIdentityAdministrationCommand>;
const result = await service.changeIdentity({
projectId: identityCommand.request.projectId,
operation: identityCommand.operation.slice('identity.'.length) as
| 'register'
| 'enable'
| 'disable',
target: identityCommand.request.target,
expectedCurrentVersion:
identityCommand.request.expectedCurrentVersion,
mutationId: identityCommand.request.mutationId,
requestId: identityCommand.request.requestId,
principal: authenticated.principal,
});
return Object.freeze({
schemaVersion: 1 as const,
operation: identityCommand.operation,
status: result.status,
projectId: identityCommand.request.projectId,
target: result.identity.subject,
version: result.identity.version,
identityStatus: result.identity.status,
});
}
if (
command.operation === 'credential.issue' ||
command.operation === 'credential.rotate'
) {
const credentialCommand =
command as Readonly<LocalApiCredentialIssueCommand>;
const active = await database.ownerPepper.resolveActive();
if (!active) {
throw new LocalIdentityCredentialCommandPepperUnavailableError();
}
const key = await database.ownerPepper.resolveKey(
active.activePepperKeyId,
);
const pepper = adapters
.createPepperProvider(
credentialCommand.options.ownerPepperKeyringDirectory,
)
.resolve(active.activePepperKeyId);
if (
!key ||
key.state !== 'active' ||
key.materialDigest !== active.materialDigest ||
!pepper ||
pepper.summary.digest !== active.materialDigest
) {
throw new LocalIdentityCredentialCommandPepperUnavailableError();
}
const material = adapters.randomBytes(API_CREDENTIAL_SECRET_BYTES);
if (
!Buffer.isBuffer(material) ||
material.byteLength !== API_CREDENTIAL_SECRET_BYTES
) {
material?.fill?.(0);
throw new LocalIdentityCredentialCommandConfigurationError(
'random source returned invalid credential material',
);
}
let prepared: Readonly<LocalCredentialAdministrationDeliveryRecord>;
try {
const delivery = adapters.createDelivery(
credentialCommand.options.credentialDeliveryDirectory,
);
prepared = delivery.prepare({
schemaVersion: 1,
kind: 'qinglong3-local-managed-credential-delivery',
mutationId: credentialCommand.request.mutationId,
requestId: credentialCommand.request.requestId,
projectId: credentialCommand.request.projectId,
subject: credentialCommand.request.target,
credentialId: credentialCommand.request.credentialId,
secret: material.toString('base64url'),
notBeforeAtMs: commandNowMs,
expiresAtMs:
commandNowMs + credentialCommand.request.lifetimeMs,
});
const deliveryDigest = delivery.digest(prepared);
const result = await service.changeCredential({
projectId: credentialCommand.request.projectId,
operation:
credentialCommand.operation === 'credential.issue'
? 'issue'
: 'rotate',
credentialId: credentialCommand.request.credentialId,
target: credentialCommand.request.target,
expectedCurrentVersion:
credentialCommand.request.expectedCurrentVersion,
pepperKeyId: active.activePepperKeyId,
secretDigest: apiCredentialSecretDigest(
pepper.pepper,
credentialCommand.request.credentialId,
prepared.secret,
),
deliveryDigest,
notBeforeAtMs: prepared.notBeforeAtMs,
expiresAtMs: prepared.expiresAtMs,
mutationId: credentialCommand.request.mutationId,
requestId: credentialCommand.request.requestId,
principal: authenticated.principal,
});
const published = delivery.publish(prepared, deliveryDigest);
return activeCredentialResult(
credentialCommand,
result,
published,
);
} finally {
material.fill(0);
}
}
if (command.operation === 'credential.revoke') {
const credentialCommand =
command as Readonly<LocalApiCredentialRevokeCommand>;
const current: Readonly<ApiCredentialRecord> | null =
await database.apiCredentials.resolve(
credentialCommand.request.credentialId,
);
if (
!current ||
!sameSubject(current.subject, credentialCommand.request.target)
) {
throw new LocalIdentityCredentialCommandCurrentCredentialError();
}
const result = await service.changeCredential({
projectId: credentialCommand.request.projectId,
operation: 'revoke',
credentialId: credentialCommand.request.credentialId,
target: credentialCommand.request.target,
expectedCurrentVersion:
credentialCommand.request.expectedCurrentVersion,
pepperKeyId: current.pepperKeyId,
mutationId: credentialCommand.request.mutationId,
requestId: credentialCommand.request.requestId,
principal: authenticated.principal,
});
return Object.freeze({
schemaVersion: 1 as const,
operation: credentialCommand.operation,
status: result.status,
projectId: credentialCommand.request.projectId,
target: result.credential.subject,
credentialId: result.credential.credentialId,
version: result.credential.version,
state: result.credential.state,
expiresAtMs: result.credential.expiresAtMs,
});
}
const acknowledgementCommand =
command as Readonly<LocalCredentialDeliveryAcknowledgeCommand>;
const result = await service.acknowledgeCredentialDelivery({
projectId: acknowledgementCommand.request.projectId,
credentialMutationId:
acknowledgementCommand.request.credentialMutationId,
expectedDeliveryDigest:
acknowledgementCommand.request.expectedDeliveryDigest,
mutationId: acknowledgementCommand.request.mutationId,
requestId: acknowledgementCommand.request.requestId,
principal: authenticated.principal,
});
const acknowledgement = result.acknowledgement;
if (
acknowledgement.credentialMutationId !==
acknowledgementCommand.request.credentialMutationId ||
acknowledgement.acknowledgementMutationId !==
acknowledgementCommand.request.mutationId ||
acknowledgement.projectId !==
acknowledgementCommand.request.projectId ||
acknowledgement.deliveryDigest !==
acknowledgementCommand.request.expectedDeliveryDigest
) {
throw new LocalIdentityCredentialAdministrationUnavailableError();
}
const cleanup = adapters
.createDelivery(
acknowledgementCommand.options.credentialDeliveryDirectory,
)
.removeAcknowledged(
acknowledgement.credentialMutationId,
acknowledgement.deliveryDigest,
);
return Object.freeze({
schemaVersion: 1 as const,
operation: acknowledgementCommand.operation,
status: result.status,
projectId: acknowledgement.projectId,
credentialMutationId: acknowledgement.credentialMutationId,
acknowledgementMutationId:
acknowledgement.acknowledgementMutationId,
deliveryDigest: acknowledgement.deliveryDigest,
cleanup,
});
} catch (error) {
const audit = failureAudit(
command,
authenticated,
error,
clock(adapters.now),
);
if (audit) {
await database.identityCredentialAdministration.record(audit);
}
throw error;
}
} finally {
await database.close();
}
},
});
}
export function runLocalIdentityCredentialCommandFile(
commandFilePath: string,
): Promise<Readonly<LocalIdentityCredentialCommandResult>> {
return createLocalIdentityCredentialCommandRunner().run(commandFilePath);
}
@@ -0,0 +1,55 @@
#!/usr/bin/env node
import { runLocalIdentityCredentialCommandFile } from './identityCredentialCommand';
const USAGE =
'Usage: ql3-identity run --command-file /absolute/private-command.json';
async function main(argv: readonly string[]): Promise<void> {
if (argv.length === 1 && (argv[0] === '--help' || argv[0] === '-h')) {
process.stdout.write(`${USAGE}\n`);
return;
}
const commandFilePath = argv[2];
if (
argv.length !== 3 ||
argv[0] !== 'run' ||
argv[1] !== '--command-file' ||
commandFilePath === undefined
) {
process.stderr.write(
`${JSON.stringify({
code: 'LOCAL_IDENTITY_CREDENTIAL_CLI_USAGE_INVALID',
message: USAGE,
})}\n`,
);
process.exitCode = 64;
return;
}
try {
const result = await runLocalIdentityCredentialCommandFile(commandFilePath);
process.stdout.write(`${JSON.stringify(result)}\n`);
} catch (error) {
const candidate = error as {
readonly code?: unknown;
readonly name?: unknown;
readonly message?: unknown;
};
process.stderr.write(
`${JSON.stringify({
code:
typeof candidate.code === 'string'
? candidate.code
: 'LOCAL_IDENTITY_CREDENTIAL_CLI_FAILED',
name: typeof candidate.name === 'string' ? candidate.name : 'Error',
message:
typeof candidate.message === 'string'
? candidate.message
: 'Local Identity credential command failed',
})}\n`,
);
process.exitCode = 1;
}
}
void main(process.argv.slice(2));
@@ -0,0 +1,23 @@
// Security management owns identity and API credential administration commands.
export {
LocalIdentityCredentialCommandConfigurationError,
LocalIdentityCredentialCommandCurrentCredentialError,
LocalIdentityCredentialCommandPepperUnavailableError,
} from './identity-credential-command/contracts';
export type {
LocalApiCredentialInspectionCommand,
LocalApiCredentialIssueCommand,
LocalApiCredentialRevokeCommand,
LocalCredentialDeliveryAcknowledgeCommand,
LocalIdentityAdministrationCommand,
LocalIdentityCredentialCommand,
LocalIdentityCredentialCommandOptions,
LocalIdentityCredentialCommandResult,
LocalIdentityCredentialCommandRunner,
LocalIdentityCredentialCommandRunnerDependencies,
LocalIdentityInspectionCommand,
} from './identity-credential-command/contracts';
export {
createLocalIdentityCredentialCommandRunner,
runLocalIdentityCredentialCommandFile,
} from './identity-credential-command/runner';
@@ -0,0 +1,55 @@
#!/usr/bin/env node
import { runLocalProjectPolicyCommandFile } from './projectPolicyCommand';
const USAGE =
'Usage: ql3-policy run --command-file /absolute/private-command.json';
async function main(argv: readonly string[]): Promise<void> {
if (argv.length === 1 && (argv[0] === '--help' || argv[0] === '-h')) {
process.stdout.write(`${USAGE}\n`);
return;
}
const commandFilePath = argv[2];
if (
argv.length !== 3 ||
argv[0] !== 'run' ||
argv[1] !== '--command-file' ||
commandFilePath === undefined
) {
process.stderr.write(
`${JSON.stringify({
code: 'LOCAL_PROJECT_POLICY_CLI_USAGE_INVALID',
message: USAGE,
})}\n`,
);
process.exitCode = 64;
return;
}
try {
const result = await runLocalProjectPolicyCommandFile(commandFilePath);
process.stdout.write(`${JSON.stringify(result)}\n`);
} catch (error) {
const candidate = error as {
readonly code?: unknown;
readonly name?: unknown;
readonly message?: unknown;
};
process.stderr.write(
`${JSON.stringify({
code:
typeof candidate.code === 'string'
? candidate.code
: 'LOCAL_PROJECT_POLICY_CLI_FAILED',
name: typeof candidate.name === 'string' ? candidate.name : 'Error',
message:
typeof candidate.message === 'string'
? candidate.message
: 'Local Project policy command failed',
})}\n`,
);
process.exitCode = 1;
}
}
void main(process.argv.slice(2));
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,55 @@
#!/usr/bin/env node
import { runLocalSecretCommandFile } from './secretCommand';
const USAGE =
'Usage: ql3-secret run --command-file /absolute/private-command.json';
async function main(argv: readonly string[]): Promise<void> {
if (argv.length === 1 && (argv[0] === '--help' || argv[0] === '-h')) {
process.stdout.write(`${USAGE}\n`);
return;
}
const commandFilePath = argv[2];
if (
argv.length !== 3 ||
argv[0] !== 'run' ||
argv[1] !== '--command-file' ||
commandFilePath === undefined
) {
process.stderr.write(
`${JSON.stringify({
code: 'LOCAL_SECRET_CLI_USAGE_INVALID',
message: USAGE,
})}\n`,
);
process.exitCode = 64;
return;
}
try {
const result = await runLocalSecretCommandFile(commandFilePath);
process.stdout.write(`${JSON.stringify(result)}\n`);
} catch (error) {
const candidate = error as {
readonly code?: unknown;
readonly name?: unknown;
readonly message?: unknown;
};
process.stderr.write(
`${JSON.stringify({
code:
typeof candidate.code === 'string'
? candidate.code
: 'LOCAL_SECRET_CLI_FAILED',
name: typeof candidate.name === 'string' ? candidate.name : 'Error',
message:
typeof candidate.message === 'string'
? candidate.message
: 'Local Secret command failed',
})}\n`,
);
process.exitCode = 1;
}
}
void main(process.argv.slice(2));
@@ -0,0 +1,475 @@
// Security management owns authenticated Local Secret mutation commands.
import path from 'node:path';
import {
PrivateLocalCommandFileError,
readPrivateLocalCommandFile,
readPrivateLocalJsonFile,
} from '@qinglong/local-command-file';
import {
AuthenticatedLocalCommandAuthenticationError,
establishAuthenticatedLocalCommand,
type AuthenticatedLocalCommand,
} from '@qinglong/local-owner-console/authenticated-command';
import { LocalSecretKeyringFileProvider } from '@qinglong/local-secret';
import {
LocalSecretAdministrationAuthenticationError,
LocalSecretAdministrationAuthorizationError,
LocalSecretAdministrationUnavailableError,
createLocalSecretAdministrationService,
} from '@qinglong/local-admin/secret-administration';
import {
LocalSqliteAuthenticatedManagementFenceError,
type LocalSqliteAuthenticatedUserCredentialFence,
} from '@qinglong/local-sqlite/authenticated-management';
import {
openLocalSqliteSecretAdministrationDatabase,
type LocalSqliteSecretAdministrationDatabase,
} from '@qinglong/local-sqlite/secret-administration';
import { LocalSecretAuthorizationFenceConflictError } from '@qinglong/runtime-core/local-secret-administration';
import {
MAX_LOCAL_SECRET_PLAINTEXT_BYTES,
LocalSecretMutationConflictError,
LocalSecretVersionConflictError,
assertLocalSecretExpectedVersion,
assertLocalSecretName,
assertLocalSecretPlaintext,
assertLocalSecretProjectId,
} from '@qinglong/runtime-core/local-secret';
import type { SecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
const MAX_PATH_BYTES = 4096;
const MAX_SECRET_VALUE_FILE_BYTES = MAX_LOCAL_SECRET_PLAINTEXT_BYTES + 1024;
const UUID_V4_PATTERN =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
const REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
export interface LocalSecretCommandOptions {
readonly deploymentRoot: string;
readonly databasePath: string;
readonly profile: 'edge' | 'standalone';
readonly ownerPepperKeyringDirectory: string;
readonly credentialFilePath: string;
readonly secretKeyringPath: string;
readonly busyTimeoutMs?: number;
}
export interface PutLocalSecretCommand {
readonly schemaVersion: 1;
readonly operation: 'secret.put';
readonly options: LocalSecretCommandOptions;
readonly request: {
readonly projectId: string;
readonly name: string;
readonly secretValueFilePath: string;
readonly mutationId: string;
readonly requestId: string;
readonly failureAuditEventId: string;
readonly expectedCurrentVersion: number;
};
}
export type LocalSecretCommand = PutLocalSecretCommand;
export type LocalSecretCommandResult = Readonly<{
schemaVersion: 1;
operation: LocalSecretCommand['operation'];
status: 'inserted' | 'existing';
version: number;
secretRef: string;
}>;
export interface LocalSecretCommandRunner {
run(commandFilePath: string): Promise<Readonly<LocalSecretCommandResult>>;
}
export interface LocalSecretCommandRunnerDependencies {
readonly openDatabase: typeof openLocalSqliteSecretAdministrationDatabase;
readonly authenticate: typeof establishAuthenticatedLocalCommand;
readonly createService: typeof createLocalSecretAdministrationService;
readonly now: () => number;
}
export class LocalSecretCommandConfigurationError extends TypeError {
readonly code = 'LOCAL_SECRET_COMMAND_CONFIGURATION_INVALID';
constructor(message: string, readonly cause?: unknown) {
super(`Local Secret command configuration is invalid: ${message}`);
this.name = 'LocalSecretCommandConfigurationError';
}
}
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 LocalSecretCommandConfigurationError(
`${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 LocalSecretCommandConfigurationError(`${label} shape is invalid`);
}
}
function boundedPath(value: unknown, label: 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 LocalSecretCommandConfigurationError(
`${label} must be a normalized bounded absolute non-root path`,
);
}
return value;
}
function descendant(root: string, candidate: string, label: string): void {
const relative = path.relative(root, candidate);
if (
relative.length === 0 ||
relative === '..' ||
relative.startsWith(`..${path.sep}`) ||
path.isAbsolute(relative)
) {
throw new LocalSecretCommandConfigurationError(
`${label} must be a descendant of deploymentRoot`,
);
}
}
function normalizeOptions(value: unknown): Readonly<LocalSecretCommandOptions> {
const hasBusyTimeout =
!!value &&
typeof value === 'object' &&
!Array.isArray(value) &&
Object.hasOwn(value, 'busyTimeoutMs');
exactObject(
value,
[
'deploymentRoot',
'databasePath',
'profile',
'ownerPepperKeyringDirectory',
'credentialFilePath',
'secretKeyringPath',
...(hasBusyTimeout ? ['busyTimeoutMs'] : []),
],
'options',
);
const deploymentRoot = boundedPath(value.deploymentRoot, 'deploymentRoot');
for (const key of [
'databasePath',
'ownerPepperKeyringDirectory',
'credentialFilePath',
'secretKeyringPath',
] as const) {
descendant(deploymentRoot, boundedPath(value[key], key), key);
}
if (value.profile !== 'edge' && value.profile !== 'standalone') {
throw new LocalSecretCommandConfigurationError(
'profile must be edge or standalone',
);
}
if (
value.busyTimeoutMs !== undefined &&
(!Number.isSafeInteger(value.busyTimeoutMs) ||
(value.busyTimeoutMs as number) < 100 ||
(value.busyTimeoutMs as number) > 30_000)
) {
throw new LocalSecretCommandConfigurationError('busyTimeoutMs is invalid');
}
return Object.freeze(value as unknown as LocalSecretCommandOptions);
}
function normalizeRequest(
value: unknown,
deploymentRoot: string,
): Readonly<PutLocalSecretCommand['request']> {
exactObject(
value,
[
'projectId',
'name',
'secretValueFilePath',
'mutationId',
'requestId',
'failureAuditEventId',
'expectedCurrentVersion',
],
'request',
);
const secretValueFilePath = boundedPath(
value.secretValueFilePath,
'secretValueFilePath',
);
descendant(deploymentRoot, secretValueFilePath, 'secretValueFilePath');
try {
assertLocalSecretProjectId(value.projectId);
assertLocalSecretName(value.name);
assertLocalSecretExpectedVersion(value.expectedCurrentVersion);
} catch (error) {
throw new LocalSecretCommandConfigurationError(
'Secret identity or expected version is invalid',
error,
);
}
if (
typeof value.mutationId !== 'string' ||
!UUID_V4_PATTERN.test(value.mutationId) ||
typeof value.failureAuditEventId !== 'string' ||
!UUID_V4_PATTERN.test(value.failureAuditEventId) ||
value.failureAuditEventId === value.mutationId ||
typeof value.requestId !== 'string' ||
!REQUEST_ID_PATTERN.test(value.requestId)
) {
throw new LocalSecretCommandConfigurationError(
'request identity is invalid',
);
}
return Object.freeze(value as unknown as PutLocalSecretCommand['request']);
}
function normalizeCommand(value: unknown): Readonly<LocalSecretCommand> {
exactObject(
value,
['schemaVersion', 'operation', 'options', 'request'],
'command',
);
if (value.schemaVersion !== 1 || value.operation !== 'secret.put') {
throw new LocalSecretCommandConfigurationError(
'command version or operation is invalid',
);
}
const options = normalizeOptions(value.options);
const request = normalizeRequest(value.request, options.deploymentRoot);
return Object.freeze({
schemaVersion: 1,
operation: 'secret.put',
options,
request,
});
}
function readCommandFile(candidatePath: string): Readonly<LocalSecretCommand> {
try {
return normalizeCommand(readPrivateLocalCommandFile(candidatePath));
} catch (error) {
if (error instanceof LocalSecretCommandConfigurationError) throw error;
throw new LocalSecretCommandConfigurationError(
'command file cannot be read',
error,
);
}
}
function readSecretValue(filePath: string): string {
try {
const value = readPrivateLocalJsonFile(filePath, {
maxBytes: MAX_SECRET_VALUE_FILE_BYTES,
});
exactObject(value, ['kind', 'schemaVersion', 'value'], 'secret value');
if (
value.schemaVersion !== 1 ||
value.kind !== 'qinglong3-local-secret-value'
) {
throw new LocalSecretCommandConfigurationError(
'secret value version or kind is invalid',
);
}
assertLocalSecretPlaintext(value.value);
return value.value;
} catch (error) {
if (error instanceof LocalSecretCommandConfigurationError) throw error;
if (error instanceof PrivateLocalCommandFileError) {
throw new LocalSecretCommandConfigurationError(
'secret value file cannot be read',
error,
);
}
throw new LocalSecretCommandConfigurationError(
'secret value is invalid',
error,
);
}
}
function failureAudit(
command: Readonly<LocalSecretCommand>,
authenticated: Readonly<AuthenticatedLocalCommand> | undefined,
error: unknown,
occurredAtMs: number,
): Readonly<SecurityAuditRecord> | null {
if (
error instanceof LocalSecretAdministrationAuthenticationError ||
error instanceof LocalSecretAdministrationAuthorizationError ||
error instanceof LocalSecretAdministrationUnavailableError
) {
return null;
}
let outcome: SecurityAuditRecord['outcome'];
let reason: string;
if (
!authenticated ||
error instanceof AuthenticatedLocalCommandAuthenticationError
) {
outcome = 'authentication_rejected';
reason = 'credential_rejected';
} else if (
error instanceof LocalSqliteAuthenticatedManagementFenceError ||
error instanceof LocalSecretAuthorizationFenceConflictError
) {
outcome = 'denied';
reason = 'credential_or_policy_fence_rejected';
} else if (error instanceof LocalSecretVersionConflictError) {
outcome = 'denied';
reason = 'current_version_conflict';
} else if (error instanceof LocalSecretMutationConflictError) {
outcome = 'denied';
reason = 'mutation_conflict';
} else if (error instanceof LocalSecretCommandConfigurationError) {
outcome = 'denied';
reason = 'secret_value_rejected';
} else {
return null;
}
return Object.freeze({
eventId: command.request.failureAuditEventId,
requestId: command.request.requestId,
operationId: 'secret.manage',
projectId: command.request.projectId,
subject: authenticated?.principal.subject ?? null,
authenticationId: authenticated?.principal.authenticationId ?? null,
outcome,
reasons: Object.freeze([reason]),
fence: null,
occurredAtMs,
});
}
function dependencies(
value: LocalSecretCommandRunnerDependencies,
): Readonly<LocalSecretCommandRunnerDependencies> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.keys(value).sort().join('\0') !==
['authenticate', 'createService', 'now', 'openDatabase']
.sort()
.join('\0') ||
typeof value.openDatabase !== 'function' ||
typeof value.authenticate !== 'function' ||
typeof value.createService !== 'function' ||
typeof value.now !== 'function'
) {
throw new LocalSecretCommandConfigurationError(
'runner dependencies are invalid',
);
}
return Object.freeze({ ...value });
}
async function activateFence(
database: LocalSqliteSecretAdministrationDatabase,
authenticated: Readonly<AuthenticatedLocalCommand>,
): Promise<void> {
await authenticated.confirm();
database.activateUserCredentialFence(
authenticated.databaseFence as Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
);
}
export function createLocalSecretCommandRunner(
candidateDependencies: LocalSecretCommandRunnerDependencies = {
openDatabase: openLocalSqliteSecretAdministrationDatabase,
authenticate: establishAuthenticatedLocalCommand,
createService: createLocalSecretAdministrationService,
now: Date.now,
},
): LocalSecretCommandRunner {
const adapters = dependencies(candidateDependencies);
return Object.freeze({
async run(commandFilePath: string) {
const command = readCommandFile(commandFilePath);
const database = await adapters.openDatabase({
databasePath: command.options.databasePath,
profile: command.options.profile,
...(command.options.busyTimeoutMs === undefined
? {}
: { busyTimeoutMs: command.options.busyTimeoutMs }),
});
let authenticated: Readonly<AuthenticatedLocalCommand> | undefined;
try {
try {
authenticated = await adapters.authenticate(database, {
deploymentRoot: command.options.deploymentRoot,
databasePath: command.options.databasePath,
ownerPepperKeyringDirectory:
command.options.ownerPepperKeyringDirectory,
credentialFilePath: command.options.credentialFilePath,
authenticationNamespace: 'local_secret',
});
await activateFence(database, authenticated);
const plaintext = readSecretValue(
command.request.secretValueFilePath,
);
await activateFence(database, authenticated);
const service = adapters.createService(
database.projectPolicy,
database.localSecretAdministration,
database.securityAudit,
new LocalSecretKeyringFileProvider(
command.options.secretKeyringPath,
),
{ now: adapters.now },
);
const result = await service.put({
projectId: command.request.projectId,
name: command.request.name,
plaintext,
mutationId: command.request.mutationId,
requestId: command.request.requestId,
expectedCurrentVersion: command.request.expectedCurrentVersion,
principal: authenticated.principal,
});
return Object.freeze({
schemaVersion: 1 as const,
operation: command.operation,
status: result.status,
version: result.version,
secretRef: result.secretRef,
});
} catch (error) {
const audit = failureAudit(
command,
authenticated,
error,
adapters.now(),
);
if (audit) await database.securityAudit.record(audit);
throw error;
}
} finally {
await database.close();
}
},
});
}
export function runLocalSecretCommandFile(
commandFilePath: string,
): Promise<Readonly<LocalSecretCommandResult>> {
return createLocalSecretCommandRunner().run(commandFilePath);
}
@@ -0,0 +1,55 @@
#!/usr/bin/env node
import { runLocalSecurityAuditQueryCommandFile } from './securityAuditQueryCommand';
const USAGE =
'Usage: ql3-audit run --command-file /absolute/private-command.json';
async function main(argv: readonly string[]): Promise<void> {
if (argv.length === 1 && (argv[0] === '--help' || argv[0] === '-h')) {
process.stdout.write(`${USAGE}\n`);
return;
}
const commandFilePath = argv[2];
if (
argv.length !== 3 ||
argv[0] !== 'run' ||
argv[1] !== '--command-file' ||
commandFilePath === undefined
) {
process.stderr.write(
`${JSON.stringify({
code: 'LOCAL_SECURITY_AUDIT_QUERY_CLI_USAGE_INVALID',
message: USAGE,
})}\n`,
);
process.exitCode = 64;
return;
}
try {
const result = await runLocalSecurityAuditQueryCommandFile(commandFilePath);
process.stdout.write(`${JSON.stringify(result)}\n`);
} catch (error) {
const candidate = error as {
readonly code?: unknown;
readonly name?: unknown;
readonly message?: unknown;
};
process.stderr.write(
`${JSON.stringify({
code:
typeof candidate.code === 'string'
? candidate.code
: 'LOCAL_SECURITY_AUDIT_QUERY_CLI_FAILED',
name: typeof candidate.name === 'string' ? candidate.name : 'Error',
message:
typeof candidate.message === 'string'
? candidate.message
: 'Local security audit query command failed',
})}\n`,
);
process.exitCode = 1;
}
}
void main(process.argv.slice(2));
@@ -0,0 +1,636 @@
// Security management owns bounded audit inspection and retention commands.
import path from 'node:path';
import {
LocalSecurityAuditQueryAuthenticationError,
LocalSecurityAuditQueryAuthorizationError,
createLocalSecurityAuditQueryService,
type LocalSecurityAuditQueryService,
} from '@qinglong/local-admin/security-audit-query';
import {
LocalSecurityAuditRetentionAuthenticationError,
LocalSecurityAuditRetentionAuthorizationError,
createLocalSecurityAuditRetentionService,
type LocalSecurityAuditRetentionService,
} from '@qinglong/local-admin/security-audit-retention';
import { readPrivateLocalCommandFile } from '@qinglong/local-command-file';
import {
AuthenticatedLocalCommandAuthenticationError,
establishAuthenticatedLocalCommand,
type AuthenticatedLocalCommand,
} from '@qinglong/local-owner-console/authenticated-command';
import {
LocalSqliteAuthenticatedManagementFenceError,
type LocalSqliteAuthenticatedUserCredentialFence,
} from '@qinglong/local-sqlite/authenticated-management';
import {
openLocalSqliteSecurityAuditQueryDatabase,
type LocalSqliteSecurityAuditQueryDatabase,
} from '@qinglong/local-sqlite/security-audit-query';
import {
LocalSecurityAuditQueryAuthorizationFenceConflictError,
LocalSecurityAuditQueryUnavailableError,
MAX_LOCAL_SECURITY_AUDIT_QUERY_PAGE_SIZE,
} from '@qinglong/runtime-core/local-security-audit-query';
import {
LocalSecurityAuditCompactionMutationConflictError,
LocalSecurityAuditRetentionAuthorizationFenceConflictError,
LocalSecurityAuditRetentionUnavailableError,
MAX_EDGE_SECURITY_AUDIT_COMPACTION_BATCH_SIZE,
MAX_LOCAL_SECURITY_AUDIT_RETENTION_MS,
MAX_STANDALONE_SECURITY_AUDIT_COMPACTION_BATCH_SIZE,
MIN_LOCAL_SECURITY_AUDIT_RETENTION_MS,
type LocalSecurityAuditCompactionRecord,
} from '@qinglong/runtime-core/local-security-audit-retention';
import { assertProjectPolicyProjectId } from '@qinglong/runtime-core/project-policy';
import type { SecurityAuditRecord } from '@qinglong/runtime-core/security-audit';
import {
normalizeSecurityAuditQuery,
type SecurityAuditQuery,
type SecurityAuditQueryCursor,
} from '@qinglong/runtime-core/security-audit-query';
const MAX_PATH_BYTES = 4096;
const UUID_V4_PATTERN =
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
const REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
export interface LocalSecurityAuditQueryCommandOptions {
readonly deploymentRoot: string;
readonly databasePath: string;
readonly profile: 'edge' | 'standalone';
readonly ownerPepperKeyringDirectory: string;
readonly credentialFilePath: string;
readonly busyTimeoutMs?: number;
}
export interface LocalSecurityAuditQueryCommand {
readonly schemaVersion: 1;
readonly operation: 'security.audit.list';
readonly options: LocalSecurityAuditQueryCommandOptions;
readonly request: {
readonly authorityProjectId: string;
readonly query: SecurityAuditQuery;
readonly requestId: string;
readonly auditEventId: string;
};
}
export interface LocalSecurityAuditCompactionCommand {
readonly schemaVersion: 1;
readonly operation: 'security.audit.compact';
readonly options: LocalSecurityAuditQueryCommandOptions;
readonly request: {
readonly authorityProjectId: string;
readonly retentionMs: number;
readonly eligibleBeforeMs: number;
readonly limit: number;
readonly mutationId: string;
readonly requestId: string;
readonly failureAuditEventId: string;
};
}
export type LocalSecurityAuditCommand =
| LocalSecurityAuditQueryCommand
| LocalSecurityAuditCompactionCommand;
export interface RedactedLocalSecurityAuditRecord {
readonly eventId: string;
readonly requestId: string;
readonly operationId: string;
readonly projectId: string | null;
readonly subject: SecurityAuditRecord['subject'];
readonly outcome: SecurityAuditRecord['outcome'];
readonly reasons: readonly string[];
readonly fence: SecurityAuditRecord['fence'];
readonly occurredAtMs: number;
}
export interface LocalSecurityAuditQueryCommandResult {
readonly schemaVersion: 1;
readonly operation: 'security.audit.list';
readonly records: readonly Readonly<RedactedLocalSecurityAuditRecord>[];
readonly nextCursor: Readonly<SecurityAuditQueryCursor> | null;
}
export interface LocalSecurityAuditCompactionCommandResult {
readonly schemaVersion: 1;
readonly operation: 'security.audit.compact';
readonly status: 'inserted' | 'existing';
readonly mutationId: string;
readonly retentionMs: number;
readonly eligibleBeforeMs: number;
readonly batchLimit: number;
readonly deletedCount: number;
readonly deletedPayloadBytes: number;
readonly first: LocalSecurityAuditCompactionRecord['first'];
readonly last: LocalSecurityAuditCompactionRecord['last'];
readonly recordsDigest: string;
readonly createdAtMs: number;
}
export type LocalSecurityAuditCommandResult =
| LocalSecurityAuditQueryCommandResult
| LocalSecurityAuditCompactionCommandResult;
export interface LocalSecurityAuditQueryCommandRunner {
run(
commandFilePath: string,
): Promise<Readonly<LocalSecurityAuditCommandResult>>;
}
export interface LocalSecurityAuditQueryCommandRunnerDependencies {
readonly openDatabase: typeof openLocalSqliteSecurityAuditQueryDatabase;
readonly authenticate: typeof establishAuthenticatedLocalCommand;
readonly createService: typeof createLocalSecurityAuditQueryService;
readonly createRetentionService: typeof createLocalSecurityAuditRetentionService;
readonly now: () => number;
}
export class LocalSecurityAuditQueryCommandConfigurationError extends TypeError {
readonly code = 'LOCAL_SECURITY_AUDIT_QUERY_COMMAND_INVALID';
constructor(message: string, readonly cause?: unknown) {
super(`Local security audit query command is invalid: ${message}`);
this.name = 'LocalSecurityAuditQueryCommandConfigurationError';
}
}
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 LocalSecurityAuditQueryCommandConfigurationError(
`${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 LocalSecurityAuditQueryCommandConfigurationError(
`${label} shape is invalid`,
);
}
}
function boundedPath(value: unknown, label: 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 LocalSecurityAuditQueryCommandConfigurationError(
`${label} must be a normalized bounded absolute non-root path`,
);
}
return value;
}
function descendant(root: string, candidate: string, label: string): void {
const relative = path.relative(root, candidate);
if (
relative.length === 0 ||
relative === '..' ||
relative.startsWith(`..${path.sep}`) ||
path.isAbsolute(relative)
) {
throw new LocalSecurityAuditQueryCommandConfigurationError(
`${label} must be a descendant of deploymentRoot`,
);
}
}
function normalizeOptions(
value: unknown,
): Readonly<LocalSecurityAuditQueryCommandOptions> {
const hasBusyTimeout =
!!value &&
typeof value === 'object' &&
!Array.isArray(value) &&
Object.hasOwn(value, 'busyTimeoutMs');
exactObject(
value,
[
'deploymentRoot',
'databasePath',
'profile',
'ownerPepperKeyringDirectory',
'credentialFilePath',
...(hasBusyTimeout ? ['busyTimeoutMs'] : []),
],
'options',
);
const deploymentRoot = boundedPath(value.deploymentRoot, 'deploymentRoot');
for (const key of [
'databasePath',
'ownerPepperKeyringDirectory',
'credentialFilePath',
] as const) {
descendant(deploymentRoot, boundedPath(value[key], key), key);
}
if (value.profile !== 'edge' && value.profile !== 'standalone') {
throw new LocalSecurityAuditQueryCommandConfigurationError(
'profile must be edge or standalone',
);
}
if (
value.busyTimeoutMs !== undefined &&
(!Number.isSafeInteger(value.busyTimeoutMs) ||
(value.busyTimeoutMs as number) < 100 ||
(value.busyTimeoutMs as number) > 30_000)
) {
throw new LocalSecurityAuditQueryCommandConfigurationError(
'busyTimeoutMs is invalid',
);
}
return Object.freeze(
value as unknown as LocalSecurityAuditQueryCommandOptions,
);
}
function normalizeCommand(value: unknown): Readonly<LocalSecurityAuditCommand> {
exactObject(
value,
['schemaVersion', 'operation', 'options', 'request'],
'command',
);
if (
value.schemaVersion !== 1 ||
(value.operation !== 'security.audit.list' &&
value.operation !== 'security.audit.compact')
) {
throw new LocalSecurityAuditQueryCommandConfigurationError(
'command version or operation is invalid',
);
}
const options = normalizeOptions(value.options);
const compact = value.operation === 'security.audit.compact';
exactObject(
value.request,
compact
? [
'authorityProjectId',
'retentionMs',
'eligibleBeforeMs',
'limit',
'mutationId',
'requestId',
'failureAuditEventId',
]
: ['authorityProjectId', 'query', 'requestId', 'auditEventId'],
'request',
);
try {
assertProjectPolicyProjectId(value.request.authorityProjectId as string);
} catch {
throw new LocalSecurityAuditQueryCommandConfigurationError(
'authority Project identity is invalid',
);
}
if (
typeof value.request.requestId !== 'string' ||
!REQUEST_ID_PATTERN.test(value.request.requestId)
) {
throw new LocalSecurityAuditQueryCommandConfigurationError(
'audit or request identity is invalid',
);
}
if (compact) {
const profileLimit =
options.profile === 'edge'
? MAX_EDGE_SECURITY_AUDIT_COMPACTION_BATCH_SIZE
: MAX_STANDALONE_SECURITY_AUDIT_COMPACTION_BATCH_SIZE;
if (
typeof value.request.mutationId !== 'string' ||
!UUID_V4_PATTERN.test(value.request.mutationId) ||
typeof value.request.failureAuditEventId !== 'string' ||
!UUID_V4_PATTERN.test(value.request.failureAuditEventId) ||
value.request.mutationId === value.request.failureAuditEventId ||
!Number.isSafeInteger(value.request.retentionMs) ||
(value.request.retentionMs as number) <
MIN_LOCAL_SECURITY_AUDIT_RETENTION_MS ||
(value.request.retentionMs as number) >
MAX_LOCAL_SECURITY_AUDIT_RETENTION_MS ||
!Number.isSafeInteger(value.request.eligibleBeforeMs) ||
(value.request.eligibleBeforeMs as number) < 0 ||
!Number.isSafeInteger(
(value.request.eligibleBeforeMs as number) +
(value.request.retentionMs as number),
) ||
!Number.isSafeInteger(value.request.limit) ||
(value.request.limit as number) < 1 ||
(value.request.limit as number) > profileLimit
) {
throw new LocalSecurityAuditQueryCommandConfigurationError(
'compaction identity, retention fence, or limit is invalid',
);
}
return Object.freeze({
schemaVersion: 1,
operation: 'security.audit.compact',
options,
request: Object.freeze({
authorityProjectId: value.request.authorityProjectId as string,
retentionMs: value.request.retentionMs as number,
eligibleBeforeMs: value.request.eligibleBeforeMs as number,
limit: value.request.limit as number,
mutationId: value.request.mutationId,
requestId: value.request.requestId,
failureAuditEventId: value.request.failureAuditEventId,
}),
});
}
if (
typeof value.request.auditEventId !== 'string' ||
!UUID_V4_PATTERN.test(value.request.auditEventId)
) {
throw new LocalSecurityAuditQueryCommandConfigurationError(
'audit identity is invalid',
);
}
let query: Readonly<SecurityAuditQuery>;
try {
query = normalizeSecurityAuditQuery(
value.request.query as SecurityAuditQuery,
);
} catch {
throw new LocalSecurityAuditQueryCommandConfigurationError(
'filter, cursor, or limit is invalid',
);
}
if (query.limit > MAX_LOCAL_SECURITY_AUDIT_QUERY_PAGE_SIZE) {
throw new LocalSecurityAuditQueryCommandConfigurationError(
'limit exceeds the local maximum of 64',
);
}
return Object.freeze({
schemaVersion: 1,
operation: 'security.audit.list',
options,
request: Object.freeze({
authorityProjectId: value.request.authorityProjectId as string,
query,
requestId: value.request.requestId,
auditEventId: value.request.auditEventId,
}),
});
}
function readCommandFile(
candidatePath: string,
): Readonly<LocalSecurityAuditCommand> {
try {
return normalizeCommand(readPrivateLocalCommandFile(candidatePath));
} catch (error) {
if (error instanceof LocalSecurityAuditQueryCommandConfigurationError) {
throw error;
}
throw new LocalSecurityAuditQueryCommandConfigurationError(
'command file cannot be read',
error,
);
}
}
function failureAudit(
command: Readonly<LocalSecurityAuditCommand>,
authenticated: Readonly<AuthenticatedLocalCommand> | undefined,
error: unknown,
occurredAtMs: number,
): Readonly<SecurityAuditRecord> | null {
if (
error instanceof LocalSecurityAuditQueryAuthorizationError ||
error instanceof LocalSecurityAuditQueryUnavailableError ||
error instanceof LocalSecurityAuditRetentionAuthorizationError ||
error instanceof LocalSecurityAuditRetentionUnavailableError
) {
return null;
}
const eventId =
command.operation === 'security.audit.compact'
? command.request.failureAuditEventId
: command.request.auditEventId;
if (
!authenticated ||
error instanceof AuthenticatedLocalCommandAuthenticationError ||
error instanceof LocalSecurityAuditQueryAuthenticationError ||
error instanceof LocalSecurityAuditRetentionAuthenticationError
) {
return Object.freeze({
eventId,
requestId: command.request.requestId,
operationId: command.operation,
projectId: command.request.authorityProjectId,
subject: authenticated?.principal.subject ?? null,
authenticationId: authenticated?.principal.authenticationId ?? null,
outcome: 'authentication_rejected',
reasons: Object.freeze(['credential_rejected']),
fence: null,
occurredAtMs,
});
}
if (
error instanceof LocalSqliteAuthenticatedManagementFenceError ||
error instanceof LocalSecurityAuditQueryAuthorizationFenceConflictError ||
error instanceof LocalSecurityAuditRetentionAuthorizationFenceConflictError
) {
return Object.freeze({
eventId,
requestId: command.request.requestId,
operationId: command.operation,
projectId: command.request.authorityProjectId,
subject: authenticated.principal.subject,
authenticationId: authenticated.principal.authenticationId,
outcome: 'denied',
reasons: Object.freeze(['credential_or_policy_fence_rejected']),
fence: null,
occurredAtMs,
});
}
if (error instanceof LocalSecurityAuditCompactionMutationConflictError) {
return Object.freeze({
eventId,
requestId: command.request.requestId,
operationId: command.operation,
projectId: command.request.authorityProjectId,
subject: authenticated?.principal.subject ?? null,
authenticationId: authenticated?.principal.authenticationId ?? null,
outcome: 'denied',
reasons: Object.freeze(['mutation_conflict']),
fence: null,
occurredAtMs,
});
}
return null;
}
function dependencies(
value: LocalSecurityAuditQueryCommandRunnerDependencies,
): Readonly<LocalSecurityAuditQueryCommandRunnerDependencies> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
Object.keys(value).sort().join('\0') !==
[
'authenticate',
'createRetentionService',
'createService',
'now',
'openDatabase',
]
.sort()
.join('\0') ||
typeof value.openDatabase !== 'function' ||
typeof value.authenticate !== 'function' ||
typeof value.createService !== 'function' ||
typeof value.createRetentionService !== 'function' ||
typeof value.now !== 'function'
) {
throw new LocalSecurityAuditQueryCommandConfigurationError(
'runner dependencies are invalid',
);
}
return Object.freeze({ ...value });
}
function redact(
record: Readonly<SecurityAuditRecord>,
): Readonly<RedactedLocalSecurityAuditRecord> {
return Object.freeze({
eventId: record.eventId,
requestId: record.requestId,
operationId: record.operationId,
projectId: record.projectId,
subject: record.subject,
outcome: record.outcome,
reasons: record.reasons,
fence: record.fence,
occurredAtMs: record.occurredAtMs,
});
}
export function createLocalSecurityAuditQueryCommandRunner(
candidateDependencies: LocalSecurityAuditQueryCommandRunnerDependencies = {
openDatabase: openLocalSqliteSecurityAuditQueryDatabase,
authenticate: establishAuthenticatedLocalCommand,
createService: createLocalSecurityAuditQueryService,
createRetentionService: createLocalSecurityAuditRetentionService,
now: Date.now,
},
): LocalSecurityAuditQueryCommandRunner {
const adapters = dependencies(candidateDependencies);
return Object.freeze({
async run(commandFilePath: string) {
const command = readCommandFile(commandFilePath);
const database = await adapters.openDatabase({
databasePath: command.options.databasePath,
profile: command.options.profile,
...(command.options.busyTimeoutMs === undefined
? {}
: { busyTimeoutMs: command.options.busyTimeoutMs }),
});
let authenticated: Readonly<AuthenticatedLocalCommand> | undefined;
try {
try {
authenticated = await adapters.authenticate(database, {
deploymentRoot: command.options.deploymentRoot,
databasePath: command.options.databasePath,
ownerPepperKeyringDirectory:
command.options.ownerPepperKeyringDirectory,
credentialFilePath: command.options.credentialFilePath,
authenticationNamespace: 'local_security_audit',
});
await authenticated.confirm();
database.activateUserCredentialFence(
authenticated.databaseFence as Readonly<LocalSqliteAuthenticatedUserCredentialFence>,
);
if (command.operation === 'security.audit.compact') {
const service: LocalSecurityAuditRetentionService =
adapters.createRetentionService(
database.projectPolicy,
database.securityAuditRetention,
{ now: adapters.now },
);
const result = await service.compact({
authorityProjectId: command.request.authorityProjectId,
retentionMs: command.request.retentionMs,
eligibleBeforeMs: command.request.eligibleBeforeMs,
limit: command.request.limit,
mutationId: command.request.mutationId,
requestId: command.request.requestId,
failureAuditEventId: command.request.failureAuditEventId,
principal: authenticated.principal,
});
return Object.freeze({
schemaVersion: 1 as const,
operation: command.operation,
status: result.status,
mutationId: result.record.mutationId,
retentionMs: result.record.retentionMs,
eligibleBeforeMs: result.record.eligibleBeforeMs,
batchLimit: result.record.batchLimit,
deletedCount: result.record.deletedCount,
deletedPayloadBytes: result.record.deletedPayloadBytes,
first: result.record.first,
last: result.record.last,
recordsDigest: result.record.recordsDigest,
createdAtMs: result.record.createdAtMs,
});
}
const service: LocalSecurityAuditQueryService =
adapters.createService(
database.projectPolicy,
database.securityAuditQuery,
{ now: adapters.now },
);
const result = await service.list({
authorityProjectId: command.request.authorityProjectId,
query: command.request.query,
auditEventId: command.request.auditEventId,
requestId: command.request.requestId,
principal: authenticated.principal,
});
return Object.freeze({
schemaVersion: 1 as const,
operation: command.operation,
records: Object.freeze(result.records.map(redact)),
nextCursor: result.nextCursor,
});
} catch (error) {
const audit = failureAudit(
command,
authenticated,
error,
adapters.now(),
);
if (audit) {
try {
await database.securityAudit.record(audit);
} catch {
throw new LocalSecurityAuditQueryUnavailableError();
}
}
throw error;
}
} finally {
await database.close();
}
},
});
}
export function runLocalSecurityAuditQueryCommandFile(
commandFilePath: string,
): Promise<Readonly<LocalSecurityAuditCommandResult>> {
return createLocalSecurityAuditQueryCommandRunner().run(commandFilePath);
}