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 Solution][Detection Alerts] Alert tagging #157786

Merged
merged 36 commits into from
Jun 21, 2023

Conversation

dplumlee
Copy link
Contributor

@dplumlee dplumlee commented May 15, 2023

Summary

Epic (internal): https://github.com/elastic/security-team/issues/3273

Adds tagging feature to alerts generated by security solution detection rules. Users will now be able to tag individual alerts via a dropdown on the alerts page or via the take action menu in the alert details flyout. They will also be able to bulk manage tags within the bulk actions menu on the alerts table. Tag options are generated from a field in the advanced settings and will be defaulted to 3 options (Duplicate, False positive, Further investigation required).

From a code standpoint, alert tags will live in a new field: kibana.alert.workflow_tags in order to avoid merging with copied source event tags in existing schema fields

Screenshots

From alert context menu

Screenshot 2023-06-13 at 12 03 32 PM

From bulk actions

Screenshot 2023-06-13 at 12 03 08 PM

Checklist

Delete any items that are not applicable to this PR.

For maintainers

@dplumlee dplumlee added Team:Detections and Resp Security Detection Response Team Feature:Detection Alerts Security Solution Detection Alerts Feature release_note:feature Makes this part of the condensed release notes Team:Detection Alerts Security Detection Alerts Area Team v8.9.0 labels May 15, 2023
@dplumlee dplumlee self-assigned this May 15, 2023
@dplumlee dplumlee force-pushed the security-alert-tags branch from d514119 to 3ad0adf Compare May 15, 2023 18:50
@@ -125,3 +127,10 @@ export const privilege = t.type({
});

export type Privilege = t.TypeOf<typeof privilege>;

export const alert_tags = t.type({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of following the rules bulk edit tags schema? It may make it easier to expand the feature by following a more reusable pattern like:

const BulkActionEditPayloadTags = t.type({
  type: t.union([
    t.literal(BulkActionEditType.add_tags),
    t.literal(BulkActionEditType.delete_tags),
    t.literal(BulkActionEditType.set_tags),
  ]),
  value: RuleTagArray,
});

That way if we decide theres a new action type we want to introduce for tags, the schema updates are minimal.

Copy link
Contributor Author

@dplumlee dplumlee May 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be open to more discussion on this pertaining to more reusable patterns and expandability, it's definitely a bit unusual, but the main reason we created it like this in the first place was because we have the intermediate state in the bulk actions tags dropdown, so we couldn't just handle setting all queried alerts to the same tags array. With the two different buckets of add and remove we can cover each of the possible use cases for each specific alert with one api call.


const { setAlertTags } = useSetAlertTags();
const initalTagsState = useMemo(() => {
const existingTags = alertIds.map(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can we extract this into a helper or util we can unit test?

Also, would using a set here simplify the number of iterations through the tags?

} else if (changedOption.checked === 'on') {
tagsToAdd[changedOption.label] = true;
delete tagsToRemove[changedOption.label];
} else if (!changedOption.checked) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are you checking if the checked property exists or if it's null or undefined?

@dplumlee dplumlee marked this pull request as ready for review May 30, 2023 17:12
@dplumlee dplumlee requested review from a team as code owners May 30, 2023 17:12
>
{isolateHostTitle}
</EuiContextMenuItem>,
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs unit test

Comment on lines 219 to 223
!isEvent && actionsData.ruleId
? [...statusActionItems, ...exceptionActionItems]
? [...statusActionItems, ...alertTagsItems, ...exceptionActionItems]
: isEndpointEvent && canCreateEndpointEventFilters
? eventFilterActionItems
: [],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add unit tests for rendering correct alerts actions items

@@ -88,5 +89,11 @@ export const getBulkActionHook =
refetch: refetchGlobalQuery,
});

return [...alertActions, timelineAction];
const { alertTagsItems, alertTagsPanels } = useBulkAlertTagsItems({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this file does not have any unit tests to add to, but it really should have them on the new and existing behavior

Copy link
Contributor

@stephmilovic stephmilovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the new feature, super cool!

With Feature Freeze pending, I won't require the requested unit tests for approval, but I would request a Github issue be created noting what tests are missing.

One thing I do have to insist be done before merging is to add a mount check to any state updates that could happen after a component has dismounted (see here)

refetch?: () => void;
}

export const useAlertTagsActions = ({ closePopover, ecsRowData, scopeId, refetch }: Props) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this hook needs unit tests


export const validateAlertTagsArrays = (tags: AlertTags) => {
const { tags_to_add: tagsToAdd, tags_to_remove: tagsToRemove } = tags;
const duplicates = tagsToAdd.filter((tag) => tagsToRemove.includes(tag));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs a little test here as well

Comment on lines +96 to +98
if (refetchQuery) refetchQuery();
if (refresh) refresh();
if (clearSelection) clearSelection();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we group all these props under a single onUpdateSuccess prop? And let the caller do whatever it needs when the update succeeded, outside this component.

}),
[alerts, isAllSelected, items, rowSelection, setIsBulkActionsLoading, clearSelection, refresh]
);
const bulkActionsPanels = useMemo(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entire file needs unit tests, especially the logic here

@@ -71,6 +72,23 @@ const getCaseAttachments = ({
return groupAlertsByRule?.(filteredAlerts) ?? [];
};

const addItemsToInitalPanel = ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const addItemsToInitalPanel = ({
const addItemsToInitialPanel = ({

Can you also pease ensure your tests hit each of these conditions, I believe only one is currently tested


const clearSelection = () => {
updateBulkActionsState({ action: BulkActionsVerbs.clear });
};
const caseBulkActions = useBulkAddToCaseActions({ casesConfig, refresh, clearSelection });

const bulkActions = [...configBulkActions, ...caseBulkActions];
const bulkActions =
caseBulkActions.length !== 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another condition we should test

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes this hook also probably runs too often as well, both the old way and new way.

try {
setIsLoading(true);
setTableLoading(true);
const response = await http.fetch<estypes.UpdateByQueryResponse>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not react-query?

Copy link
Contributor

@kqualters-elastic kqualters-elastic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new warning when I apply a tag:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
    at BulkAlertTagsPanelComponent (http://localhost:5601/9007199254740991/bundles/plugin/securitySolution/1.0.0/securitySolution.chunk.lazy_register_alerts_table_configuration.js:107579:3)
    at div
    at EuiResizeObserver (http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:128128:81)
    at div
    at EuiContextMenuPanel (http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:84005:81)
    at div
    at EuiContextMenu (http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:83483:81)
    at div
    at EuiMutationObserver (http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:127891:81)
    at div
    at http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:166047:73
    at EuiPanel (http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:131939:23)
    at http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:166047:73
    at EuiPopoverPanel (http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:133242:23)
    at div
    at http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:366533:59
    at FocusLockUI (http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:362836:71)
    at http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:363739:60
    at EuiFocusTrap (http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:105315:81)
    at EuiPortal (http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:133710:81)
    at div
    at http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:166047:73
    at EuiPopover (http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:132478:81)
    at div
    at forwardRef (http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:423669:12)
    at div
    at AlertContextMenuComponent (http://localhost:5601/9007199254740991/bundles/plugin/securitySolution/1.0.0/securitySolution.chunk.lazy_register_alerts_table_configuration.js:142978:3)
    at ConnectFunction (http://localhost:5601/9007199254740991/bundles/plugin/securitySolution/1.0.0/securitySolution.chunk.3.js:422:41)
    at div
    at forwardRef (http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:423669:12)
    at ActionsComponent (http://localhost:5601/9007199254740991/bundles/plugin/securitySolution/1.0.0/securitySolution.chunk.lazy_register_alerts_table_configuration.js:95250:3)
    at RowActionComponent (http://localhost:5601/9007199254740991/bundles/plugin/securitySolution/1.0.0/securitySolution.chunk.lazy_register_alerts_table_configuration.js:85295:3)
    at div
    at http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:166047:73
    at http://localhost:5601/9007199254740991/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js:104071:23
    at rowCellRender (http://localhost:5601/9007199254740991/bundles/plugin/triggersActionsUi/1.0.0/triggersActionsUi.chunk.24.js:1030:13)

Copy link
Contributor

@kqualters-elastic kqualters-elastic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree with needing additional unit tests, also should fix the warning(s) that appear, but overall feature works alright locally and any bugs/missing tests/warning fixes can be fixed post FF 👍

Copy link
Contributor

@stephmilovic stephmilovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @kqualters-elastic about the console warnings, that likely means we didn't catch the state updates on something dismounted. I strongly suggest updating the query to be managed either by useFetch or react-query to reduce risk of bad state updates. I added a comment to your unit test PR to address the console warnings as well. Thank you for the changes!

LGTM, please ping me directly for the follow up PR.

@kibana-ci
Copy link
Collaborator

💛 Build succeeded, but was flaky

Failed CI Steps

Test Failures

  • [job] [logs] FTR Configs #17 / apis index_patterns scripted_fields_deprecations scripted field deprecations no scripted fields deprecations

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
securitySolution 4201 4206 +5

Public APIs missing comments

Total count of every public API that lacks a comment. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats comments for more detailed information.

id before after diff
@kbn/rule-data-utils 103 104 +1

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
apm 3.6MB 3.6MB +45.0B
infra 2.0MB 2.0MB +45.0B
observability 1.0MB 1.0MB +45.0B
securitySolution 11.0MB 11.0MB +6.4KB
triggersActionsUi 1.4MB 1.4MB +518.0B
total +7.1KB

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
apm 36.0KB 36.0KB +60.0B
infra 119.7KB 119.7KB +60.0B
observability 86.0KB 86.1KB +60.0B
securitySolution 51.4KB 51.9KB +475.0B
triggersActionsUi 85.7KB 85.7KB +60.0B
total +715.0B
Unknown metric groups

API count

id before after diff
@kbn/rule-data-utils 106 107 +1

ESLint disabled line counts

id before after diff
enterpriseSearch 13 15 +2
securitySolution 411 415 +4
total +6

Total ESLint disabled count

id before after diff
enterpriseSearch 14 16 +2
securitySolution 494 498 +4
total +6

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

cc @dplumlee

@dplumlee dplumlee merged commit 0f57260 into elastic:main Jun 21, 2023
@kibanamachine kibanamachine added the backport:skip This commit does not require backporting label Jun 21, 2023
@dplumlee dplumlee deleted the security-alert-tags branch July 4, 2023 05:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:skip This commit does not require backporting Feature:Detection Alerts Security Solution Detection Alerts Feature release_note:feature Makes this part of the condensed release notes Team:Detection Alerts Security Detection Alerts Area Team Team:Detections and Resp Security Detection Response Team v8.9.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.