diff --git a/spec/fixtures/bastion_fixtures.ts b/spec/fixtures/bastion_fixtures.ts new file mode 100644 index 0000000..aae11f7 --- /dev/null +++ b/spec/fixtures/bastion_fixtures.ts @@ -0,0 +1,48 @@ +import { Fixture } from './general_fixtures' + +export const aBastionLogCloudWatchEvent: Fixture = { + input: { + deliveryStreamArn: 'someDeliveryStreamArn', + invocationId: 'someId', + region: 'eu-west-1', + records: [{ + approximateArrivalTimestamp: 1234, + recordId: 'LogEvent-1111', + data: Buffer.from(JSON.stringify({ + owner: '123456789', + logGroup: 'test-12_bastion', + logStream: 'bastionTaskId', + subscriptionFilters: [], + messageType: 'DATA_MESSAGE', + logEvents: [ + { + id: 'cloudwatch-log-message-id-1', + timestamp: '1234', + message: '{"time":"2025-03-28T09:41:30.493596011Z","level":"INFO","msg":"Container started. Starting bastion service."}' + } + ] + })).toString('base64') + } + ] + }, + expected: { + records: [{ + result: 'Ok', + recordId: 'LogEvent-1111', + data: Buffer.from([ + { + host: 'bastionTaskId', + source: 'bastion', + sourcetype: 'linux_bastion', + index: 'pay_host', + event: '{"time":"2025-03-28T09:41:30.493596011Z","level":"INFO","msg":"Container started. Starting bastion service."}', + fields: { + account: 'test', + environment: 'test-12' + }, + time: 1743154890.493 + } + ].map(x => JSON.stringify(x)).join('\n')).toString('base64') + }] + } +} diff --git a/spec/index.test.ts b/spec/index.test.ts index fe28707..9417aff 100644 --- a/spec/index.test.ts +++ b/spec/index.test.ts @@ -6,6 +6,10 @@ import { anInvalidApplicationLogFirehoseTransformationEventRecord } from './fixtures/application_fixtures' +import { + aBastionLogCloudWatchEvent +} from './fixtures/bastion_fixtures' + import { aConcourseSyslogCloudWatchEvent, aConcourseAuditCloudWatchEvent, @@ -76,6 +80,16 @@ describe('Processing CloudWatchLogEvents', () => { expect(Buffer.from(result.records[1].data as string, 'base64').toString()).toEqual(Buffer.from(expectedSecondRecord.data as string, 'base64').toString()) }) }) + describe('From Bastion', () => { + test('should transform bastion logs from CloudWatch', async () => { + const result = await handler(aBastionLogCloudWatchEvent.input, mockContext, mockCallback) as FirehoseTransformationResult + + const expectedFirstRecord = aBastionLogCloudWatchEvent.expected.records[0] + expect(result.records[0].result).toEqual(expectedFirstRecord.result) + expect(result.records[0].recordId).toEqual(expectedFirstRecord.recordId) + expect(Buffer.from(result.records[0].data as string, 'base64').toString()).toEqual(Buffer.from(expectedFirstRecord.data as string, 'base64').toString()) + }) + }) describe('From Nginx', () => { test('should transform nginx forward proxy logs from CloudWatch', async () => { diff --git a/src/extractTime.ts b/src/extractTime.ts index e4a6be0..5e588a5 100644 --- a/src/extractTime.ts +++ b/src/extractTime.ts @@ -29,6 +29,16 @@ export function extractAppLogTime(log: string): number | undefined { return parseStringToEpoch(extractedTime[1]) } +export function extractBastionLogTime(log: string): number | undefined { + const regex = /"time"\s*:\s*"(.*?)"/ + const extractedTime = regexTimeFromLog(regex, log) + if (extractedTime === undefined) { + return undefined + } + + return parseStringToEpoch(extractedTime[1]) +} + export function extractSquidLogTime(log: string): number | undefined { let extractedTime = regexTimeFromLog(SQUID_ACCESS_LOG_FORMAT_REGEX, log) if (extractedTime !== undefined) { @@ -133,6 +143,8 @@ export function parseTimeFromLog(log: string, logType: CloudWatchLogTypes): numb switch (logType) { case CloudWatchLogTypes.app: return extractAppLogTime(log) + case CloudWatchLogTypes.bastion: + return extractBastionLogTime(log) case CloudWatchLogTypes.squid: return extractSquidLogTime(log) case CloudWatchLogTypes.syslog: diff --git a/src/transformData.ts b/src/transformData.ts index c16c695..603b270 100644 --- a/src/transformData.ts +++ b/src/transformData.ts @@ -152,6 +152,8 @@ function sourceTypeFromLogGroup(logType: CloudWatchLogTypes, msg: string): strin switch (logType) { case CloudWatchLogTypes.app: return 'ST004:application_json' + case CloudWatchLogTypes.bastion: + return 'linux_bastion' case CloudWatchLogTypes['nginx-forward-proxy']: case CloudWatchLogTypes['nginx-reverse-proxy']: return 'nginx:plus:kv' @@ -181,6 +183,8 @@ function indexFromLogType(logType: CloudWatchLogTypes): string { switch (logType) { case CloudWatchLogTypes.app: return 'pay_application' + case CloudWatchLogTypes.bastion: + return 'pay_host' case CloudWatchLogTypes['nginx-forward-proxy']: case CloudWatchLogTypes['nginx-reverse-proxy']: return 'pay_ingress' diff --git a/src/types.ts b/src/types.ts index 40060eb..32bfda7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -41,6 +41,7 @@ export enum CloudWatchLogTypes { 'apt', 'audit', 'auth', + 'bastion', 'concourse', 'cloudtrail', 'dmesg',