mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-23 03:18:09 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
|
||||
const {
|
||||
LocalSecurityAuditRetentionAuthorizationError,
|
||||
LocalSecurityAuditRetentionConfigurationError,
|
||||
createLocalSecurityAuditRetentionService,
|
||||
} = require('@qinglong/local-admin/security-audit-retention');
|
||||
const {
|
||||
LocalSecurityAuditRetentionAuthorizationFenceConflictError,
|
||||
MIN_LOCAL_SECURITY_AUDIT_RETENTION_MS,
|
||||
} = require('@qinglong/runtime-core/local-security-audit-retention');
|
||||
|
||||
const NOW = 4_000_000_000;
|
||||
const PRINCIPAL = Object.freeze({
|
||||
subject: Object.freeze({ type: 'user', id: 'owner-user' }),
|
||||
authenticationId: 'local_security_audit:test',
|
||||
authenticatedAtMs: NOW - 1_000,
|
||||
expiresAtMs: NOW + 60_000,
|
||||
assurance: 'local_console',
|
||||
});
|
||||
|
||||
function projectPolicy(role = 'owner') {
|
||||
return {
|
||||
async resolve(projectId, subject) {
|
||||
return {
|
||||
project: {
|
||||
id: projectId,
|
||||
name: 'Default',
|
||||
slug: 'default',
|
||||
status: 'active',
|
||||
version: 4,
|
||||
createdAtMs: 0,
|
||||
updatedAtMs: 0,
|
||||
},
|
||||
binding: {
|
||||
projectId,
|
||||
subject,
|
||||
version: 7,
|
||||
state: 'active',
|
||||
role,
|
||||
mutationId: 'owner-binding',
|
||||
changedBy: subject,
|
||||
createdAtMs: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
async append() {
|
||||
throw new Error('not used');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function request(overrides = {}) {
|
||||
return {
|
||||
authorityProjectId: 'default',
|
||||
retentionMs: MIN_LOCAL_SECURITY_AUDIT_RETENTION_MS,
|
||||
eligibleBeforeMs: NOW - MIN_LOCAL_SECURITY_AUDIT_RETENTION_MS,
|
||||
limit: 64,
|
||||
mutationId: 'a1000000-0000-4000-8000-000000000001',
|
||||
requestId: 'audit-compact-1',
|
||||
failureAuditEventId: 'a1000000-0000-4000-8000-000000000002',
|
||||
principal: PRINCIPAL,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test('authorizes an instance Owner and binds the exact retention command', async () => {
|
||||
let command;
|
||||
const repository = {
|
||||
async resolveCompaction() {
|
||||
return null;
|
||||
},
|
||||
async compactAuthorized(value) {
|
||||
command = value;
|
||||
return {
|
||||
status: 'inserted',
|
||||
record: {
|
||||
mutationId: value.mutationId,
|
||||
requestId: value.requestId,
|
||||
authorityProjectId: value.authorization.authorityProjectId,
|
||||
retentionMs: value.retentionMs,
|
||||
eligibleBeforeMs: value.eligibleBeforeMs,
|
||||
batchLimit: value.limit,
|
||||
deletedCount: 0,
|
||||
deletedPayloadBytes: 0,
|
||||
first: null,
|
||||
last: null,
|
||||
recordsDigest: 'a'.repeat(64),
|
||||
createdAtMs: value.audit.occurredAtMs,
|
||||
},
|
||||
audit: value.audit,
|
||||
};
|
||||
},
|
||||
async record() {
|
||||
throw new Error('not used');
|
||||
},
|
||||
};
|
||||
const service = createLocalSecurityAuditRetentionService(
|
||||
projectPolicy(),
|
||||
repository,
|
||||
{ now: () => NOW },
|
||||
);
|
||||
const result = await service.compact(request());
|
||||
assert.equal(result.status, 'inserted');
|
||||
assert.deepEqual(command.authorization, {
|
||||
authorityProjectId: 'default',
|
||||
actor: PRINCIPAL.subject,
|
||||
fence: { projectVersion: 4, bindingVersion: 7 },
|
||||
});
|
||||
assert.deepEqual(command.audit, {
|
||||
eventId: request().mutationId,
|
||||
requestId: request().requestId,
|
||||
operationId: 'security.audit.compact',
|
||||
projectId: 'default',
|
||||
subject: PRINCIPAL.subject,
|
||||
authenticationId: PRINCIPAL.authenticationId,
|
||||
outcome: 'allowed',
|
||||
reasons: ['instance_authority_security_audit_compaction'],
|
||||
fence: { projectVersion: 4, bindingVersion: 7 },
|
||||
occurredAtMs: NOW,
|
||||
});
|
||||
});
|
||||
|
||||
test('rejects an unsafe retention fence and oversized batch before repository access', async () => {
|
||||
let accessed = false;
|
||||
const service = createLocalSecurityAuditRetentionService(
|
||||
projectPolicy(),
|
||||
{
|
||||
async resolveCompaction() {
|
||||
accessed = true;
|
||||
},
|
||||
async compactAuthorized() {
|
||||
accessed = true;
|
||||
},
|
||||
async record() {
|
||||
accessed = true;
|
||||
},
|
||||
},
|
||||
{ now: () => NOW },
|
||||
);
|
||||
await assert.rejects(
|
||||
service.compact(
|
||||
request({
|
||||
eligibleBeforeMs: NOW - MIN_LOCAL_SECURITY_AUDIT_RETENTION_MS + 1,
|
||||
}),
|
||||
),
|
||||
LocalSecurityAuditRetentionConfigurationError,
|
||||
);
|
||||
await assert.rejects(
|
||||
service.compact(request({ limit: 513 })),
|
||||
LocalSecurityAuditRetentionConfigurationError,
|
||||
);
|
||||
assert.equal(accessed, false);
|
||||
});
|
||||
|
||||
test('records denial with the failure identity for a non-Owner', async () => {
|
||||
const audits = [];
|
||||
let compacted = false;
|
||||
const service = createLocalSecurityAuditRetentionService(
|
||||
projectPolicy('viewer'),
|
||||
{
|
||||
async resolveCompaction() {
|
||||
return null;
|
||||
},
|
||||
async compactAuthorized() {
|
||||
compacted = true;
|
||||
throw new Error('must not run');
|
||||
},
|
||||
async record(audit) {
|
||||
audits.push(audit);
|
||||
},
|
||||
},
|
||||
{ now: () => NOW },
|
||||
);
|
||||
await assert.rejects(
|
||||
service.compact(request()),
|
||||
LocalSecurityAuditRetentionAuthorizationError,
|
||||
);
|
||||
assert.equal(compacted, false);
|
||||
assert.equal(audits.length, 1);
|
||||
assert.equal(audits[0].eventId, request().failureAuditEventId);
|
||||
assert.equal(audits[0].outcome, 'denied');
|
||||
assert.deepEqual(audits[0].reasons, ['permission_missing']);
|
||||
});
|
||||
|
||||
test('preserves the final credential and authority fence conflict', async () => {
|
||||
const service = createLocalSecurityAuditRetentionService(
|
||||
projectPolicy(),
|
||||
{
|
||||
async resolveCompaction() {
|
||||
return null;
|
||||
},
|
||||
async compactAuthorized() {
|
||||
throw new LocalSecurityAuditRetentionAuthorizationFenceConflictError();
|
||||
},
|
||||
async record() {},
|
||||
},
|
||||
{ now: () => NOW },
|
||||
);
|
||||
await assert.rejects(
|
||||
service.compact(request()),
|
||||
LocalSecurityAuditRetentionAuthorizationFenceConflictError,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user