Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Security alert details page visualize graph #5

Closed
Show file tree
Hide file tree
Changes from all commits
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
38 changes: 38 additions & 0 deletions packages/kbn-rule-data-utils/src/technical_field_names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const KIBANA_NAMESPACE = 'kibana' as const;

const ALERT_NAMESPACE = `${KIBANA_NAMESPACE}.alert` as const;
const ALERT_RULE_NAMESPACE = `${ALERT_NAMESPACE}.rule` as const;
const ALERT_RULE_THREAT_NAMESPACE = `${ALERT_RULE_NAMESPACE}.threat` as const;

const ECS_VERSION = 'ecs.version' as const;
const EVENT_ACTION = 'event.action' as const;
Expand Down Expand Up @@ -68,6 +69,23 @@ const ALERT_RULE_TYPE_ID = `${ALERT_RULE_NAMESPACE}.rule_type_id` as const;
const ALERT_RULE_UPDATED_AT = `${ALERT_RULE_NAMESPACE}.updated_at` as const;
const ALERT_RULE_UPDATED_BY = `${ALERT_RULE_NAMESPACE}.updated_by` as const;
const ALERT_RULE_VERSION = `${ALERT_RULE_NAMESPACE}.version` as const;

// Fields pertaining to the threat tactic associated with the rule
const ALERT_THREAT_FRAMEWORK = `${ALERT_RULE_THREAT_NAMESPACE}.framework` as const;
const ALERT_THREAT_TACTIC_ID = `${ALERT_RULE_THREAT_NAMESPACE}.tactic.id` as const;
const ALERT_THREAT_TACTIC_NAME = `${ALERT_RULE_THREAT_NAMESPACE}.tactic.name` as const;
const ALERT_THREAT_TACTIC_REFERENCE = `${ALERT_RULE_THREAT_NAMESPACE}.tactic.reference` as const;
const ALERT_THREAT_TECHNIQUE_ID = `${ALERT_RULE_THREAT_NAMESPACE}.technique.id` as const;
const ALERT_THREAT_TECHNIQUE_NAME = `${ALERT_RULE_THREAT_NAMESPACE}.technique.name` as const;
const ALERT_THREAT_TECHNIQUE_REFERENCE =
`${ALERT_RULE_THREAT_NAMESPACE}.technique.reference` as const;
const ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_ID =
`${ALERT_RULE_THREAT_NAMESPACE}.technique.subtechnique.id` as const;
const ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_NAME =
`${ALERT_RULE_THREAT_NAMESPACE}.technique.subtechnique.name` as const;
const ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_REFERENCE =
`${ALERT_RULE_THREAT_NAMESPACE}.technique.subtechnique.reference` as const;

// the feature instantiating a rule type.
// Rule created in stack --> alerts
// Rule created in siem --> siem
Expand Down Expand Up @@ -137,6 +155,16 @@ const fields = {
ALERT_WORKFLOW_USER,
ALERT_RULE_UUID,
ALERT_RULE_CATEGORY,
ALERT_THREAT_FRAMEWORK,
ALERT_THREAT_TACTIC_ID,
ALERT_THREAT_TACTIC_NAME,
ALERT_THREAT_TACTIC_REFERENCE,
ALERT_THREAT_TECHNIQUE_ID,
ALERT_THREAT_TECHNIQUE_NAME,
ALERT_THREAT_TECHNIQUE_REFERENCE,
ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_ID,
ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_NAME,
ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_REFERENCE,
SPACE_IDS,
VERSION,
};
Expand Down Expand Up @@ -195,6 +223,16 @@ export {
KIBANA_NAMESPACE,
ALERT_RULE_UUID,
ALERT_RULE_CATEGORY,
ALERT_THREAT_FRAMEWORK,
ALERT_THREAT_TACTIC_ID,
ALERT_THREAT_TACTIC_NAME,
ALERT_THREAT_TACTIC_REFERENCE,
ALERT_THREAT_TECHNIQUE_ID,
ALERT_THREAT_TECHNIQUE_NAME,
ALERT_THREAT_TECHNIQUE_REFERENCE,
ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_ID,
ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_NAME,
ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_REFERENCE,
TAGS,
TIMESTAMP,
SPACE_IDS,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/graph/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"kibanaVersion": "kibana",
"server": true,
"ui": true,
"extraPublicDirs": ["public/components/graph_visualization", "public/services/workspace"],
"requiredPlugins": [
"licensing",
"data",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ function GraphWorkspace(options) {
const visibleNodes = self.nodes.filter(function (n) {
return n.parent === undefined;
});
console.log("VISIBLE NODES: ", visibleNodes);
//reset then roll-up all the counts
const allNodes = self.nodes;
allNodes.forEach((node) => {
Expand Down Expand Up @@ -890,6 +891,7 @@ function GraphWorkspace(options) {
self.addUndoLogEntry(lastOps);
}

console.log("MERGED!!: ", this);
this.runLayout();
};

Expand Down
10 changes: 10 additions & 0 deletions x-pack/plugins/graph/public/services/workspace/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { Workspace, WorkspaceOptions } from '../../types';

declare function createWorkspace(options: WorkspaceOptions): Workspace;
8 changes: 8 additions & 0 deletions x-pack/plugins/graph/public/services/workspace/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export * from './graph_client_workspace';
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ export const allowedExperimentalValues = Object.freeze({
* Enables endpoint package level rbac
*/
endpointRbacEnabled: false,

/**
* Enables the alert details page currently only accessible via the alert details flyout
*/
alertDetailsPageEnabled: false,
});

type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ export enum TimelineId {
networkPageEvents = 'network-page-events',
hostsPageSessions = 'hosts-page-sessions-v2', // the v2 is to cache bust localstorage settings as default columns were reworked.
detectionsRulesDetailsPage = 'detections-rules-details-page',
detectionsAlertDetailsPage = 'detections-alert-details-page',
detectionsPage = 'detections-page',
active = 'timeline-1',
casePage = 'timeline-case',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ describe('Alerts timeline', () => {
});

it('should not allow user with read only privileges to attach alerts to cases', () => {
// Disabled actions for read only users are hidden, so actions button should not show
cy.get(TIMELINE_CONTEXT_MENU_BTN).should('not.exist');
// Disabled actions for read only users are hidden, so only open alert details button should show
expandFirstAlertActions();
cy.get(ATTACH_ALERT_TO_CASE_BUTTON).should('not.exist');
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { expandFirstAlert } from '../../tasks/alerts';
import { setStartDate } from '../../tasks/date_picker';
import { closeTimeline } from '../../tasks/timeline';
import { createCustomRuleEnabled } from '../../tasks/api_calls/rules';
import { cleanKibana } from '../../tasks/common';
import { waitForAlertsToPopulate } from '../../tasks/create_new_rule';
import { login, visitWithoutDateRange } from '../../tasks/login';

import { getNewRule } from '../../objects/rule';
import type { CustomRule } from '../../objects/rule';

import { ALERTS_URL } from '../../urls/navigation';
import {
ALERT_DETAILS_PAGE_BACK_TO_ALERTS,
OPEN_ALERT_DETAILS_PAGE_CONTEXT_MENU_BTN,
TIMELINE_CONTEXT_MENU_BTN,
} from '../../screens/alerts';
import { PAGE_TITLE } from '../../screens/common/page';
import { OPEN_ALERT_DETAILS_PAGE } from '../../screens/alerts_details';

describe('Alert Details Page Navigation', () => {
describe('navigating to alert details page', () => {
let rule: CustomRule;
before(() => {
rule = getNewRule();
cleanKibana();
login();
createCustomRuleEnabled(rule, 'rule1');
visitWithoutDateRange(ALERTS_URL);
const dateContainingAllEvents = 'Jul 27, 2015 @ 00:00:00.000';
setStartDate(dateContainingAllEvents);
waitForAlertsToPopulate();
});

afterEach(() => {
closeTimeline();
});

it('should navigate to the details page from the alert context menu', () => {
cy.get(TIMELINE_CONTEXT_MENU_BTN).first().click();
// It opens in a new tab by default, for testing purposes, we want it to open in the same tab
cy.get(OPEN_ALERT_DETAILS_PAGE_CONTEXT_MENU_BTN).invoke('removeAttr', 'target').click();
cy.get(PAGE_TITLE).should('contain.text', rule.name);
cy.url().should('include', '/summary');
});

it('should navigate to the details page from the alert flyout', () => {
visitWithoutDateRange(ALERTS_URL);
waitForAlertsToPopulate();
expandFirstAlert();
// It opens in a new tab by default, for testing purposes, we want it to open in the same tab
cy.get(OPEN_ALERT_DETAILS_PAGE).invoke('removeAttr', 'target').click();
cy.get(PAGE_TITLE).should('contain.text', rule.name);
cy.url().should('include', '/summary');
});

it('should navigate back to the alert table from the details page', () => {
visitWithoutDateRange(ALERTS_URL);
waitForAlertsToPopulate();
expandFirstAlert();
// It opens in a new tab by default, for testing purposes, we want it to open in the same tab
cy.get(OPEN_ALERT_DETAILS_PAGE).invoke('removeAttr', 'target').click();
cy.get(ALERT_DETAILS_PAGE_BACK_TO_ALERTS).click();
cy.url().should('include', ALERTS_URL);
cy.url().should('not.include', 'summary');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ describe('Display not found page', () => {
visit(TIMELINES_URL);
});

it('navigates to the alerts page with incorrect link', () => {
// TODO: We need to determine what we want the behavior to be here
it.skip('navigates to the alerts page with incorrect link', () => {
visit(`${ALERTS_URL}/randomUrl`);
cy.get(NOT_FOUND).should('exist');
});
Expand Down
6 changes: 6 additions & 0 deletions x-pack/plugins/security_solution/cypress/screens/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ export const OPEN_ALERT_BTN = '[data-test-subj="open-alert-status"]';

export const OPENED_ALERTS_FILTER_BTN = '[data-test-subj="openAlerts"]';

export const OPEN_ALERT_DETAILS_PAGE_CONTEXT_MENU_BTN =
'[data-test-subj="open-alert-details-page-menu-item"]';

export const ALERT_DETAILS_PAGE_BACK_TO_ALERTS =
'[data-test-subj="alert-details-back-to-alerts-link"]';

export const PROCESS_NAME_COLUMN = '[data-test-subj="dataGridHeaderCell-process.name"]';
export const PROCESS_NAME = '[data-test-subj="formatted-field-process.name"]';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,5 @@ export const INSIGHTS_RELATED_ALERTS_BY_ANCESTRY = `[data-test-subj='related-ale
export const INSIGHTS_INVESTIGATE_ANCESTRY_ALERTS_IN_TIMELINE_BUTTON = `[data-test-subj='investigate-ancestry-in-timeline']`;

export const ENRICHED_DATA_ROW = `[data-test-subj='EnrichedDataRow']`;

export const OPEN_ALERT_DETAILS_PAGE = `[data-test-subj="open-alert-details-page"]`;
1 change: 1 addition & 0 deletions x-pack/plugins/security_solution/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"embeddable",
"eventLog",
"features",
"graph",
"inspector",
"kubernetesSecurity",
"lens",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
@font-face {
font-family: 'FontAwesome';
src: url('~font-awesome/fonts/fontawesome-webfont.eot?v=4.7.0');
src: url('~font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),
url('~font-awesome/fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),
url('~font-awesome/fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),
url('~font-awesome/fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),
url('~font-awesome/fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
font-weight: normal;
font-style: normal;
}

@import 'font-awesome/scss/variables';
@import 'font-awesome/scss/core';
@import 'font-awesome/scss/icons';

// new file icon
.#{$fa-css-prefix}-file-new-o:before { content: $fa-var-file-o; }
.#{$fa-css-prefix}-file-new-o:after { content: $fa-var-plus; position: relative; margin-left: -1.0em; font-size: .5em; }

// alias for alert types - allows class="fa fa-{{alertType}}"
.fa-success:before { content: $fa-var-check; }
.fa-danger:before { content: $fa-var-exclamation-circle; }

/**
* THE SVG Graph
* 1. Calculated px values come from the open/closed state of the global nav sidebar
*/

#graphBasic {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
}

.gphGraph__container {
display: flex;
flex-direction: column;
background: $euiColorEmptyShade;
position: relative;
flex: 1;
}

.gphGraph__menus {
margin: $euiSizeS;
}

.gphGraph__flexGroup {
display: flex;
width: 100%;
}

.gphGraph__flexGroupFiller {
flex: 1 1 auto;
}

@mixin gphSvgText() {
font-family: $euiFontFamily;
font-size: $euiSizeS;
line-height: $euiSizeM;
fill: $euiColorDarkShade;
color: $euiColorDarkShade;
}

.gphVisualization {
flex: 1;
display: flex;
flex-direction: column;
}

.gphGraph {
flex: 1;
overflow: hidden;
}

.gphEdge {
fill: $euiColorMediumShade;
stroke: $euiColorMediumShade;
stroke-width: 2;
stroke-opacity: .5;

&--selected {
stroke: $euiColorDarkShade;
stroke-opacity: .95;
}
}

.gphEdge--clickable {
fill: transparent;
opacity: 0;
}

.gphEdge--wrapper:hover {
.gphEdge {
stroke-opacity: .95;
cursor: pointer;
}
}

.gphNode {
cursor: pointer;
}

.gphNode__label {
@include gphSvgText;
cursor: pointer;
&--html {
@include euiTextTruncate;
text-align: center;
}
}

.gphNode__markerCircle {
fill: $euiColorDarkShade;
stroke: $euiColorEmptyShade;
}

.gphNode__markerText {
@include gphSvgText;
font-size: $euiSizeS - 2px;
fill: $euiColorEmptyShade;
}

.gphNode__circle {
fill: $euiColorMediumShade;
&--selected {
stroke-width: $euiSizeXS;
stroke: transparentize($euiColorPrimary, .25);
}
}

.gphNode__text {
fill: $euiColorInk;

&--inverse {
fill: $euiColorGhost;
}
}
Loading