wulf-pulse/lib/services/b2/client.test.ts

184 lines
5.6 KiB
TypeScript
Raw Normal View History

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
OBJECT_KEY_REGEX,
EML_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('EML_OBJECT_KEY_REGEX', () => {
it('accepts phishing/<reportId>/<attachmentId>.eml', () => {
expect(
EML_OBJECT_KEY_REGEX.test('phishing/ba03268b-5528-4dde-ad76-867523446ecd/555.eml')
).toBe(true);
expect(EML_OBJECT_KEY_REGEX.test('phishing/report_1/attachment_1.eml')).toBe(true);
});
it('rejects path traversal', () => {
expect(EML_OBJECT_KEY_REGEX.test('phishing/../evil.eml')).toBe(false);
expect(EML_OBJECT_KEY_REGEX.test('phishing/report/../../escape.eml')).toBe(false);
});
it('rejects wrong extension and the LogLift shape', () => {
expect(EML_OBJECT_KEY_REGEX.test('phishing/a/b.json')).toBe(false);
expect(
EML_OBJECT_KEY_REGEX.test(
'ba03268b-5528-4dde-ad76-867523446ecd/unknown-server/eventlogs_20251202_173301.json.gz'
)
).toBe(false);
});
});
describe('presignUpload with a custom keyRegex', () => {
it('succeeds for a valid .eml key validated against EML_OBJECT_KEY_REGEX', () => {
const url = presignUpload(
'phishing/report_1/attachment_1.eml',
1800,
FIXTURE_CFG,
EML_OBJECT_KEY_REGEX
);
expect(url).toContain('X-Amz-Expires=1800');
});
it('throws B2InvalidObjectKeyError for a LogLift-shaped key when validated against EML_OBJECT_KEY_REGEX', () => {
expect(() =>
presignUpload(
'site/host/eventlogs_20260502_120000.json.gz',
1800,
FIXTURE_CFG,
EML_OBJECT_KEY_REGEX
)
).toThrow(B2InvalidObjectKeyError);
});
it('still validates against OBJECT_KEY_REGEX by default (existing LogLift call sites unchanged)', () => {
expect(() =>
presignUpload('phishing/report_1/attachment_1.eml', 1800, FIXTURE_CFG)
).toThrow(B2InvalidObjectKeyError);
});
});
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);
});
});