Skip to content

Commit b6ddab9

Browse files
committed
Added nodes limit parameter to the API
1 parent 18df3a7 commit b6ddab9

4 files changed

Lines changed: 63 additions & 20 deletions

File tree

  • x-pack
    • packages/kbn-cloud-security-posture/common/schema/graph
    • plugins/cloud_security_posture/server/routes/graph
    • test/cloud_security_posture_api/routes

x-pack/packages/kbn-cloud-security-posture/common/schema/graph/v1.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
import { schema } from '@kbn/config-schema';
99

1010
export const graphRequestSchema = schema.object({
11+
nodesLimit: schema.maybe(schema.number()),
12+
showUnknownTarget: schema.maybe(schema.boolean()),
1113
query: schema.object({
1214
eventIds: schema.arrayOf(schema.string()),
13-
showUnknownTarget: schema.maybe(schema.boolean()),
1415
// TODO: use zod for range validation instead of config schema
1516
start: schema.oneOf([schema.number(), schema.string()]),
1617
end: schema.oneOf([schema.number(), schema.string()]),

x-pack/plugins/cloud_security_posture/server/routes/graph/route.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,8 @@ export const defineGraphRoute = (router: CspRouter) =>
3838
},
3939
},
4040
async (context, request, response) => {
41-
const {
42-
eventIds,
43-
start,
44-
end,
45-
esQuery,
46-
showUnknownTarget = false,
47-
} = request.body.query as GraphRequest['query'];
41+
const { nodesLimit, showUnknownTarget = false } = request.body;
42+
const { eventIds, start, end, esQuery } = request.body.query as GraphRequest['query'];
4843
const cspContext = await context.csp;
4944
const spaceId = (await cspContext.spaces?.spacesService?.getActiveSpace(request))?.id;
5045

@@ -55,13 +50,14 @@ export const defineGraphRoute = (router: CspRouter) =>
5550
esClient: cspContext.esClient,
5651
},
5752
query: {
58-
showUnknownTarget,
5953
eventIds,
6054
spaceId,
6155
start,
6256
end,
6357
esQuery,
6458
},
59+
showUnknownTarget,
60+
nodesLimit,
6561
});
6662

6763
return response.ok({ body: { nodes, edges } });

x-pack/plugins/cloud_security_posture/server/routes/graph/v1.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,21 @@ interface LabelEdges {
4343
interface GetGraphParams {
4444
services: GraphContextServices;
4545
query: {
46-
showUnknownTarget: boolean;
4746
eventIds: string[];
4847
spaceId?: string;
4948
start: string | number;
5049
end: string | number;
5150
esQuery?: EsQuery;
5251
};
52+
showUnknownTarget: boolean;
53+
nodesLimit?: number;
5354
}
5455

5556
export const getGraph = async ({
5657
services: { esClient, logger },
57-
query: { eventIds, showUnknownTarget, spaceId = 'default', start, end, esQuery },
58+
query: { eventIds, spaceId = 'default', start, end, esQuery },
59+
showUnknownTarget,
60+
nodesLimit,
5861
}: GetGraphParams): Promise<{
5962
nodes: NodeDataModel[];
6063
edges: EdgeDataModel[];
@@ -72,22 +75,31 @@ export const getGraph = async ({
7275
});
7376

7477
// Convert results into set of nodes and edges
75-
const graphContext = parseRecords(logger, results.records);
78+
const graphContext = parseRecords(logger, results.records, nodesLimit);
7679

7780
return { nodes: graphContext.nodes, edges: graphContext.edges };
7881
};
7982

8083
interface ParseContext {
81-
nodesMap: Record<string, NodeDataModel>;
82-
edgesMap: Record<string, EdgeDataModel>;
83-
edgeLabelsNodes: Record<string, string[]>;
84-
labelEdges: Record<string, LabelEdges>;
84+
readonly nodesLimit?: number;
85+
readonly nodesMap: Record<string, NodeDataModel>;
86+
readonly edgesMap: Record<string, EdgeDataModel>;
87+
readonly edgeLabelsNodes: Record<string, string[]>;
88+
readonly labelEdges: Record<string, LabelEdges>;
89+
readonly logger: Logger;
8590
}
8691

87-
const parseRecords = (logger: Logger, records: GraphEdge[]): GraphContext => {
88-
const ctx: ParseContext = { nodesMap: {}, edgeLabelsNodes: {}, edgesMap: {}, labelEdges: {} };
92+
const parseRecords = (logger: Logger, records: GraphEdge[], nodesLimit?: number): GraphContext => {
93+
const ctx: ParseContext = {
94+
nodesLimit,
95+
logger,
96+
nodesMap: {},
97+
edgeLabelsNodes: {},
98+
edgesMap: {},
99+
labelEdges: {},
100+
};
89101

90-
logger.trace(`Parsing records [length: ${records.length}]`);
102+
logger.trace(`Parsing records [length: ${records.length}] [nodesLimit: ${nodesLimit ?? 'none'}]`);
91103

92104
createNodes(records, ctx);
93105
createEdgesAndGroups(ctx);
@@ -205,6 +217,15 @@ const createNodes = (records: GraphEdge[], context: Omit<ParseContext, 'edgesMap
205217
const { nodesMap, edgeLabelsNodes, labelEdges } = context;
206218

207219
for (const record of records) {
220+
if (context.nodesLimit !== undefined && Object.keys(nodesMap).length >= context.nodesLimit) {
221+
context.logger.debug(
222+
`Reached nodes limit [limit: ${context.nodesLimit}] [current: ${
223+
Object.keys(nodesMap).length
224+
}]`
225+
);
226+
break;
227+
}
228+
208229
const { ips, hosts, users, actorIds, action, targetIds, isAlert, eventOutcome } = record;
209230
const actorIdsArray = castArray(actorIds);
210231
const targetIdsArray = castArray(targetIds);

x-pack/test/cloud_security_posture_api/routes/graph.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,11 +416,11 @@ export default function (providerContext: FtrProviderContext) {
416416

417417
it('Should return unknown targets', async () => {
418418
const response = await postGraph(supertest, {
419+
showUnknownTarget: true,
419420
query: {
420421
eventIds: [],
421422
start: '2024-09-01T00:00:00Z',
422423
end: '2024-09-02T00:00:00Z',
423-
showUnknownTarget: true,
424424
esQuery: {
425425
bool: {
426426
filter: [
@@ -438,6 +438,31 @@ export default function (providerContext: FtrProviderContext) {
438438
expect(response.body).to.have.property('nodes').length(3);
439439
expect(response.body).to.have.property('edges').length(2);
440440
});
441+
442+
it('Should limit number of nodes', async () => {
443+
const response = await postGraph(supertest, {
444+
nodesLimit: 1,
445+
query: {
446+
eventIds: [],
447+
start: '2024-09-01T00:00:00Z',
448+
end: '2024-09-02T00:00:00Z',
449+
esQuery: {
450+
bool: {
451+
filter: [
452+
{
453+
exists: {
454+
field: 'actor.entity.id',
455+
},
456+
},
457+
],
458+
},
459+
},
460+
},
461+
}).expect(result(200));
462+
463+
expect(response.body).to.have.property('nodes').length(3); // Minimal number of nodes in a single relationship
464+
expect(response.body).to.have.property('edges').length(2);
465+
});
441466
});
442467
});
443468
}

0 commit comments

Comments
 (0)