- RMM Overshell (migration 077): admin page, dispatch UI, executor/worker, target resolver, script registry (AD/DHCP/DNS/event-log/services/software/network/loglift) - LogLift evidence pipeline (migration 078): upload webhook, B2 storage client, receiver/matcher, EventLogCollector PowerShell script - IT Glue audit + write-back (migrations 075, 076): asset-audit runner, ticket xrefs, applications/configurations browse pages + apply/revert/audit endpoints - Link-aware analyzer bundles (migration 073) + provider toggle (migration 074): link-discovery service, OpenRouter LLM provider, related-tickets/itglue-suggestion panels, analyze-bundle endpoint - Endpoint data model + device-link reconciliation (migrations 079, 080): conflicts admin page, reconciler service, resolve endpoints - Dashboard overhaul: integration-health service + alerts, overview/health endpoints - Permissions: add itglue + rmm scopes; middleware: public /api/rmm/loglift route Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
130 lines
3.9 KiB
TypeScript
130 lines
3.9 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import {
|
|
OBJECT_KEY_REGEX,
|
|
presignDownload,
|
|
presignUpload,
|
|
B2InvalidObjectKeyError,
|
|
type B2Config,
|
|
_B2_INTERNALS,
|
|
} from './client';
|
|
|
|
const FIXTURE_CFG: B2Config = {
|
|
keyId: 'AKIA-FIXTURE',
|
|
secret: 'sec-fixture',
|
|
bucket: 'wulf-audits',
|
|
region: 'us-west-002',
|
|
endpoint: 's3.us-west-002.backblazeb2.com',
|
|
};
|
|
|
|
describe('OBJECT_KEY_REGEX', () => {
|
|
it('accepts the production shape', () => {
|
|
expect(
|
|
OBJECT_KEY_REGEX.test(
|
|
'ba03268b-5528-4dde-ad76-867523446ecd/unknown-server/eventlogs_20251202_173301.json.gz'
|
|
)
|
|
).toBe(true);
|
|
expect(
|
|
OBJECT_KEY_REGEX.test(
|
|
'site_uuid_short/MISYS-SQL/eventlogs_20260502_120000.json.gz'
|
|
)
|
|
).toBe(true);
|
|
});
|
|
|
|
it('rejects path traversal', () => {
|
|
expect(OBJECT_KEY_REGEX.test('../etc/passwd')).toBe(false);
|
|
expect(OBJECT_KEY_REGEX.test('site/../../escape/eventlogs_1.json.gz')).toBe(false);
|
|
});
|
|
|
|
it('rejects wrong shapes', () => {
|
|
expect(OBJECT_KEY_REGEX.test('site/host/something.json.gz')).toBe(false); // missing eventlogs_ prefix
|
|
expect(OBJECT_KEY_REGEX.test('eventlogs_1.json.gz')).toBe(false); // missing prefix dirs
|
|
expect(OBJECT_KEY_REGEX.test('site/host/eventlogs_1.json')).toBe(false); // missing .gz
|
|
expect(OBJECT_KEY_REGEX.test('site host/x/eventlogs_1.json.gz')).toBe(false); // space in client id
|
|
});
|
|
});
|
|
|
|
describe('presignDownload + presignUpload', () => {
|
|
const realDate = Date;
|
|
beforeEach(() => {
|
|
// Pin time so signatures are deterministic.
|
|
const fixed = new Date('2026-05-02T20:00:00.000Z');
|
|
vi.stubGlobal(
|
|
'Date',
|
|
class extends realDate {
|
|
constructor(...args: unknown[]) {
|
|
if (args.length === 0) {
|
|
super(fixed.getTime());
|
|
} else {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
super(...(args as [any]));
|
|
}
|
|
}
|
|
static now() {
|
|
return fixed.getTime();
|
|
}
|
|
} as unknown as DateConstructor
|
|
);
|
|
});
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it('produces a stable presigned GET URL', () => {
|
|
const url = presignDownload(
|
|
'site/host/eventlogs_20260502_120000.json.gz',
|
|
600,
|
|
FIXTURE_CFG
|
|
);
|
|
expect(url).toContain('https://s3.us-west-002.backblazeb2.com/wulf-audits/');
|
|
expect(url).toContain('X-Amz-Algorithm=AWS4-HMAC-SHA256');
|
|
expect(url).toContain('X-Amz-Credential=AKIA-FIXTURE');
|
|
expect(url).toContain('X-Amz-Date=20260502T200000Z');
|
|
expect(url).toContain('X-Amz-Expires=600');
|
|
expect(url).toContain('X-Amz-SignedHeaders=host');
|
|
expect(url).toMatch(/X-Amz-Signature=[a-f0-9]{64}$/);
|
|
});
|
|
|
|
it('produces a presigned PUT URL with PUT method scope', () => {
|
|
const url = presignUpload(
|
|
'site/host/eventlogs_20260502_120000.json.gz',
|
|
1800,
|
|
FIXTURE_CFG
|
|
);
|
|
expect(url).toContain('X-Amz-Expires=1800');
|
|
expect(url).toMatch(/X-Amz-Signature=[a-f0-9]{64}$/);
|
|
});
|
|
|
|
it('rejects path-traversal object keys', () => {
|
|
expect(() =>
|
|
presignDownload('../etc/eventlogs_1.json.gz', 600, FIXTURE_CFG)
|
|
).toThrow(B2InvalidObjectKeyError);
|
|
});
|
|
|
|
it('different methods produce different signatures (sanity check)', () => {
|
|
const get = presignDownload(
|
|
'site/host/eventlogs_20260502_120000.json.gz',
|
|
600,
|
|
FIXTURE_CFG
|
|
);
|
|
const put = presignUpload(
|
|
'site/host/eventlogs_20260502_120000.json.gz',
|
|
600,
|
|
FIXTURE_CFG
|
|
);
|
|
const sigGet = get.split('X-Amz-Signature=')[1];
|
|
const sigPut = put.split('X-Amz-Signature=')[1];
|
|
expect(sigGet).not.toBe(sigPut);
|
|
});
|
|
});
|
|
|
|
describe('deriveSigningKey', () => {
|
|
it('produces a 32-byte HMAC-SHA256 chain', () => {
|
|
const k = _B2_INTERNALS.deriveSigningKey(
|
|
'sec-fixture',
|
|
'20260502',
|
|
'us-west-002',
|
|
's3'
|
|
);
|
|
expect(k.length).toBe(32);
|
|
});
|
|
});
|