Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions event-stream-processing/src/parsers/apache.parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,50 @@ describe('ApacheParser', () => {
);
expect(service.applyRegex).toHaveBeenCalledTimes(1);
});

it('should parse a standard HTTP request line', () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
const regexService = new RegexService({ debug: () => {} } as any);
const parser = new ApacheParser(regexService);
const doc: OsDocument = {
data: {
'@metadata': { apacheAccessLog: true },
event: {
original:
'127.0.0.1 - - [01/Sep/2025:10:00:00 -0700] "GET /foo HTTP/1.1" 200 123 "-" "curl/7.68.0" 5',
},
},
} as unknown as OsDocument;

parser.apply(doc);

expect(doc.data.http?.request?.method).toBe('GET');
expect(doc.data.url?.original).toBe('/foo');
expect(doc.data.http?.version).toBe('1.1');
expect(doc.data.http?.request?.body).toBeUndefined();
});

it('should parse a JSON-RPC body', () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
const regexService = new RegexService({ debug: () => {} } as any);
const parser = new ApacheParser(regexService);
const doc: OsDocument = {
data: {
'@metadata': { apacheAccessLog: true },
event: {
original:
'v1.0 20120211 "https://142.34.217.234:443" "152.32.189.121" [01/Sep/2025:05:41:17 -0700] "{\\"id\\":1,\\"jsonrpc\\":\\"2.0\\",\\"method\\":\\"login\\",\\"params\\":{\\"param1\\":\\"blue1\\",\\"param2\\":\\"none\\",\\"agent\\":\\"Windows NT 6.1; Win64; x64\\"}}\\n" 400 482 bytes 6141 bytes "-" "-" 0 ms, "TLSv1.2" "ECDHE-RSA-AES256-GCM-SHA384"',
},
},
} as unknown as OsDocument;

parser.apply(doc);

expect(doc.data.http?.request?.method).toBeUndefined();
expect(doc.data.http?.request?.body?.content).toBe(
'{"id":1,"jsonrpc":"2.0","method":"login","params":{"param1":"blue1","param2":"none","agent":"Windows NT 6.1; Win64; x64"}}\n',
);
expect(doc.data.network?.protocol).toBe('rpc');
expect(doc.data.network?.application).toBe('jsonrpc');
});
});
52 changes: 33 additions & 19 deletions event-stream-processing/src/parsers/apache.parser.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { injectable } from 'inversify';
import lodash from 'lodash';
import { Parser } from '../types/parser';
import { inject } from 'inversify';
import { TYPES } from '../inversify.types';
import { OsDocument } from '../types/os-document';
import { RegexService } from '../shared/regex.service';
import lodash from 'lodash';

/* eslint-disable max-len,camelcase,@typescript-eslint/no-unsafe-call */
const regex_v1 =
/^(?<extract_format>v1\.0) (?<service__version>[^ ]+) "(?<url__scheme>[^:]+):\/\/(?<url__domain>[^:]+):(?<url__port>\d+)" "(?<source__ip>[^"]+)" \[(?<extract_timestamp>[^\]]+)\] "(?<extract_httpRequest>([^"]|(?<=\\)")*)" (?<http__response__status_code>(-?|\d+)) (?<http__request__bytes>(-?|\d+)) bytes (?<http__response__bytes>(-?|\d+)) bytes "(?<http__request__referrer>([^"]|(?<=\\)")*)" "(?<user_agent__original>([^"]|(?<=\\)")*)" (?<event__duration>\d+) ms, "(?<tls__version_protocol>[^"]+)" "(?<tls__cipher>[^"]+)"$/;
/^(?<extract_format>v1\.0) (?<service__version>[^ ]+) "(?<url__scheme>[^:]+):\/\/(?<url__domain>[^:]+):(?<url__port>\d+)" "(?<source__ip>[^"]+)" \[(?<extract_timestamp>[^\]]+)\] "(?<extract_httpRequest>.*?)(?<!\\)" (?<http__response__status_code>(-?|\d+)) (?<http__request__bytes>(-?|\d+)) bytes (?<http__response__bytes>(-?|\d+)) bytes "(?<http__request__referrer>([^"]|(?<=\\)")*)" "(?<user_agent__original>([^"]|(?<=\\)")*)" (?<event__duration>\d+) ms, "(?<tls__version_protocol>[^"]+)" "(?<tls__cipher>[^"]+)"$/;
const regex_apache_standard01 =
/^(?<source__ip>[^ ]+) ([^ ]+) (?<user__name>[^ ]+) \[(?<extract_timestamp>[^\]]+)\] "(?<extract_httpRequest>([^"]|(?<=\\)")*)" (?<http__response__status_code>(-?|\d+)) (?<http__response__bytes>(-?|\d+)) "(?<http__request__referrer>([^"]|(?<=\\)")*)" "(?<user_agent__original>([^"]|(?<=\\)")*)" (?<event__duration>(-?|\d+))$/;
const regex_apache_standard02 =
Expand Down Expand Up @@ -55,27 +55,41 @@ export class ApacheParser implements Parser {
);

if (!lodash.isNil(extractedFields.httpRequest)) {
const value = extractedFields.httpRequest;
const firstSpace = value.indexOf(' ');
const lastSpace = value.lastIndexOf(' ');
if (firstSpace > 0 && lastSpace > firstSpace) {
const httpVersion = value.substring(lastSpace).trim();
lodash.set(
document.data,
'http.request.method',
value.substring(0, firstSpace),
);
if (httpVersion.toUpperCase().startsWith('HTTP/')) {
let value = extractedFields.httpRequest.trim();

// Case 1: Looks like a normal HTTP request line
if (/^[A-Z]+\s+.+\s+HTTP\/\d/.test(value)) {
const firstSpace = value.indexOf(' ');
const lastSpace = value.lastIndexOf(' ');
if (firstSpace > 0 && lastSpace > firstSpace) {
const httpVersion = value.substring(lastSpace).trim();
lodash.set(
document.data,
'http.version',
httpVersion.substring('HTTP/'.length),
'http.request.method',
value.substring(0, firstSpace),
);
if (httpVersion.toUpperCase().startsWith('HTTP/')) {
lodash.set(
document.data,
'http.version',
httpVersion.substring('HTTP/'.length),
);
}
const uriOriginal: string = value
.substring(firstSpace, lastSpace)
.trim();
lodash.set(document.data, 'url.original', uriOriginal);
}
} else {
// Case 2: Not an HTTP request line. Treat as body
value = value.replace(/\\"/g, '"').replace(/\\n/g, '\n');

lodash.set(document.data, 'http.request.body.content', value);
// Optional: detect JSON-RPC
if (value.startsWith('{') && value.includes('"jsonrpc"')) {
lodash.set(document.data, 'network.protocol', 'rpc');
lodash.set(document.data, 'network.application', 'jsonrpc');
}
const uriOriginal: string = value
.substring(firstSpace, lastSpace)
.trim();
lodash.set(document.data, 'url.original', uriOriginal);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions event-stream-processing/src/parsers/url-explode.parser.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { injectable } from 'inversify';
import { Parser } from '../types/parser';
import lodash from 'lodash';
import * as path from 'path';
import { format as formatUrl, URL } from 'url';
import { Parser } from '../types/parser';
import { OsDocument } from '../types/os-document';
import { ParserError } from '../util/parser.error';

Expand Down Expand Up @@ -56,7 +56,7 @@ export class UrlExplodeParser implements Parser {
lodash.unset(document.data.url, 'domain');
lodash.unset(document.data.url, 'full');
}
} else if (urlOriginal === '*') {
} else if (urlOriginal === '*' || urlOriginal === '%.') {
// Do nothing
} else if (regexUrlWithProtocol.exec(urlOriginal)) {
lodash.merge(document.data.url, this.explodeURL(new URL(urlOriginal)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"ecs_host_8.11",
"ecs_http_8.11",
"ecs_log_8.11",
"ecs_network_8.11",
"ecs_organization_8.11",
"ecs_service_8.11",
"ecs_source_8.11",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"ecs_host_8.11",
"ecs_http_8.11",
"ecs_log_8.11",
"ecs_network_8.11",
"ecs_organization_8.11",
"ecs_service_8.11",
"ecs_source_8.11",
Expand Down
2 changes: 1 addition & 1 deletion workflow-cli/src/services/opensearch-template.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default class OpenSearchTemplateService extends AwsService {
}
const basename = path.basename(filePath, '.json');
// Read ECS file
// Replaces match_only_text with text as OpenSearch does not support it.
// Replace unsupported types with OpenSearch supported ones
const text = fs
.readFileSync(path.resolve(componentDir, filePath), {
encoding: 'utf8',
Expand Down