Skip to content

Commit 3a322fd

Browse files
[Cloud Security] Graph visualization and API (#195307)
## Summary This PR adds: - Graph visualization component using `xyflow`, and layouts the graph using `dagre`. - API that supports the graph visualization - API tests - Serverless API tests **List of open issues (will be tracked in a different ticket):** - Identify if `related.hosts`, `related.ip` and `related.user` are mapped before the query. (can be fixed by elastic/elasticsearch#112912) - Update nodes rendering to match recent figma changes - Return 404 when feature is not enabled - Add keyboard accessibility - Resolve axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) ### How to test You can view the graph using storybook's [playground](https://supreme-adventure-8qjmlp1.pages.github.io/graph-storybook/?path=/story/components-graph-components-dagree-layout-graph--graph-stacked-edge-cases). To test this PR you can run ``` yarn storybook cloud_security_posture_packages ``` To test the API you can use the mocked data ```bash node scripts/es_archiver load x-pack/test/cloud_security_posture_api/es_archives/logs_gcp_audit \ --es-url http://elastic:changeme@localhost:9200 \ --kibana-url http://elastic:changeme@localhost:5601 ``` And through dev tools: ``` POST kbn:/internal/cloud_security_posture/graph?apiVersion=1 { "query": { "actorIds": ["admin@example.com"], "eventIds": [""], "start": "now-1y/y", "end": "now/d" } } ``` ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit be0eadf)
1 parent 4951ab9 commit 3a322fd

66 files changed

Lines changed: 4611 additions & 27 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ x-pack/plugins/cloud_integrations/cloud_links @elastic/kibana-core
9292
x-pack/plugins/cloud @elastic/kibana-core
9393
x-pack/packages/kbn-cloud-security-posture @elastic/kibana-cloud-security-posture
9494
x-pack/packages/kbn-cloud-security-posture-common @elastic/kibana-cloud-security-posture
95+
x-pack/packages/kbn-cloud-security-posture/graph @elastic/kibana-cloud-security-posture
9596
x-pack/plugins/cloud_security_posture @elastic/kibana-cloud-security-posture
9697
packages/shared-ux/code_editor/impl @elastic/appex-sharedux
9798
packages/shared-ux/code_editor/mocks @elastic/appex-sharedux

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
"@aws-crypto/util": "^5.2.0",
107107
"@babel/runtime": "^7.24.7",
108108
"@cfworker/json-schema": "^1.12.7",
109+
"@dagrejs/dagre": "^1.1.4",
109110
"@dnd-kit/core": "^6.1.0",
110111
"@dnd-kit/sortable": "^8.0.0",
111112
"@dnd-kit/utilities": "^3.2.2",
@@ -219,6 +220,7 @@
219220
"@kbn/cloud-plugin": "link:x-pack/plugins/cloud",
220221
"@kbn/cloud-security-posture": "link:x-pack/packages/kbn-cloud-security-posture",
221222
"@kbn/cloud-security-posture-common": "link:x-pack/packages/kbn-cloud-security-posture-common",
223+
"@kbn/cloud-security-posture-graph": "link:x-pack/packages/kbn-cloud-security-posture/graph",
222224
"@kbn/cloud-security-posture-plugin": "link:x-pack/plugins/cloud_security_posture",
223225
"@kbn/code-editor": "link:packages/shared-ux/code_editor/impl",
224226
"@kbn/code-editor-mock": "link:packages/shared-ux/code_editor/mocks",
@@ -1054,6 +1056,7 @@
10541056
"@turf/length": "^6.0.2",
10551057
"@xstate/react": "^3.2.2",
10561058
"@xstate5/react": "npm:@xstate/react@^4.1.2",
1059+
"@xyflow/react": "^12.3.0",
10571060
"adm-zip": "^0.5.9",
10581061
"ai": "^2.2.33",
10591062
"ajv": "^8.12.0",
@@ -1309,6 +1312,7 @@
13091312
"@babel/plugin-transform-class-properties": "^7.24.7",
13101313
"@babel/plugin-transform-logical-assignment-operators": "^7.24.7",
13111314
"@babel/plugin-transform-numeric-separator": "^7.24.7",
1315+
"@babel/plugin-transform-optional-chaining": "^7.24.8",
13121316
"@babel/plugin-transform-runtime": "^7.24.7",
13131317
"@babel/preset-env": "^7.24.7",
13141318
"@babel/preset-react": "^7.24.7",

renovate.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,24 @@
536536
"labels": ["Team:Cloud Security", "release_note:skip", "backport:skip"],
537537
"minimumReleaseAge": "7 days",
538538
"enabled": true
539+
},
540+
{
541+
"groupName": "@xyflow/react",
542+
"matchPackageNames": ["@xyflow/react"],
543+
"reviewers": ["team:kibana-cloud-security-posture"],
544+
"matchBaseBranches": ["main"],
545+
"labels": ["Team:Cloud Security", "release_note:skip", "backport:skip"],
546+
"minimumReleaseAge": "7 days",
547+
"enabled": true
548+
},
549+
{
550+
"groupName": "@dagrejs/dagre",
551+
"matchPackageNames": ["@dagrejs/dagre"],
552+
"reviewers": ["team:kibana-cloud-security-posture"],
553+
"matchBaseBranches": ["main"],
554+
"labels": ["Team:Cloud Security", "release_note:skip", "backport:skip"],
555+
"minimumReleaseAge": "7 days",
556+
"enabled": true
539557
}
540558
],
541559
"customManagers": [

src/dev/storybook/aliases.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const storybookAliases = {
1616
canvas: 'x-pack/plugins/canvas/storybook',
1717
cases: 'packages/kbn-cases-components/.storybook',
1818
cell_actions: 'packages/kbn-cell-actions/.storybook',
19+
cloud_security_posture_packages: 'x-pack/packages/kbn-cloud-security-posture/storybook/config',
1920
cloud: 'packages/cloud/.storybook',
2021
coloring: 'packages/kbn-coloring/.storybook',
2122
language_documentation_popover: 'packages/kbn-language-documentation/.storybook',

tsconfig.base.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@
178178
"@kbn/cloud-security-posture/*": ["x-pack/packages/kbn-cloud-security-posture/*"],
179179
"@kbn/cloud-security-posture-common": ["x-pack/packages/kbn-cloud-security-posture-common"],
180180
"@kbn/cloud-security-posture-common/*": ["x-pack/packages/kbn-cloud-security-posture-common/*"],
181+
"@kbn/cloud-security-posture-graph": ["x-pack/packages/kbn-cloud-security-posture/graph"],
182+
"@kbn/cloud-security-posture-graph/*": ["x-pack/packages/kbn-cloud-security-posture/graph/*"],
181183
"@kbn/cloud-security-posture-plugin": ["x-pack/plugins/cloud_security_posture"],
182184
"@kbn/cloud-security-posture-plugin/*": ["x-pack/plugins/cloud_security_posture/*"],
183185
"@kbn/code-editor": ["packages/shared-ux/code_editor/impl"],
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
export * as graphV1 from './v1';
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
export * from './v1';
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { schema } from '@kbn/config-schema';
9+
10+
export const graphRequestSchema = schema.object({
11+
query: schema.object({
12+
actorIds: schema.arrayOf(schema.string()),
13+
eventIds: schema.arrayOf(schema.string()),
14+
// TODO: use zod for range validation instead of config schema
15+
start: schema.oneOf([schema.number(), schema.string()]),
16+
end: schema.oneOf([schema.number(), schema.string()]),
17+
}),
18+
});
19+
20+
export const graphResponseSchema = () =>
21+
schema.object({
22+
nodes: schema.arrayOf(
23+
schema.oneOf([entityNodeDataSchema, groupNodeDataSchema, labelNodeDataSchema])
24+
),
25+
edges: schema.arrayOf(edgeDataSchema),
26+
});
27+
28+
export const colorSchema = schema.oneOf([
29+
schema.literal('primary'),
30+
schema.literal('danger'),
31+
schema.literal('warning'),
32+
]);
33+
34+
export const nodeShapeSchema = schema.oneOf([
35+
schema.literal('hexagon'),
36+
schema.literal('pentagon'),
37+
schema.literal('ellipse'),
38+
schema.literal('rectangle'),
39+
schema.literal('diamond'),
40+
schema.literal('label'),
41+
schema.literal('group'),
42+
]);
43+
44+
export const nodeBaseDataSchema = schema.object({
45+
id: schema.string(),
46+
label: schema.maybe(schema.string()),
47+
icon: schema.maybe(schema.string()),
48+
});
49+
50+
export const entityNodeDataSchema = schema.allOf([
51+
nodeBaseDataSchema,
52+
schema.object({
53+
color: colorSchema,
54+
shape: schema.oneOf([
55+
schema.literal('hexagon'),
56+
schema.literal('pentagon'),
57+
schema.literal('ellipse'),
58+
schema.literal('rectangle'),
59+
schema.literal('diamond'),
60+
]),
61+
}),
62+
]);
63+
64+
export const groupNodeDataSchema = schema.allOf([
65+
nodeBaseDataSchema,
66+
schema.object({
67+
shape: schema.literal('group'),
68+
}),
69+
]);
70+
71+
export const labelNodeDataSchema = schema.allOf([
72+
nodeBaseDataSchema,
73+
schema.object({
74+
source: schema.string(),
75+
target: schema.string(),
76+
shape: schema.literal('label'),
77+
parentId: schema.maybe(schema.string()),
78+
color: colorSchema,
79+
}),
80+
]);
81+
82+
export const edgeDataSchema = schema.object({
83+
id: schema.string(),
84+
source: schema.string(),
85+
sourceShape: nodeShapeSchema,
86+
target: schema.string(),
87+
targetShape: nodeShapeSchema,
88+
color: colorSchema,
89+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
export * as graphV1 from './v1';
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
export * from './v1';

0 commit comments

Comments
 (0)