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][Exceptions] Rule exceptions TTL - Expiration #145180

Merged
merged 38 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b44af38
inital poc
dplumlee Nov 14, 2022
089a826
fixes types and adds error state
dplumlee Nov 16, 2022
a4dcd29
fix types
dplumlee Nov 16, 2022
36e79bf
fixes types again
dplumlee Nov 16, 2022
9587252
adds expires label to exception list
dplumlee Nov 16, 2022
b2ef7e7
adds exception list expire ui
dplumlee Dec 8, 2022
1c4b9d1
adds error states
dplumlee Dec 8, 2022
672dba2
fixes types
dplumlee Dec 8, 2022
a3f9d09
updates css
dplumlee Dec 15, 2022
35aa3da
fixes test mock
dplumlee Dec 15, 2022
fd9656a
updates tests
dplumlee Dec 15, 2022
43d0e4f
adds export modals
dplumlee Jan 4, 2023
906f50c
[CI] Auto-commit changed files from 'node scripts/precommit_hook.js -…
kibanamachine Jan 4, 2023
753dd4e
updates modal tests
dplumlee Jan 4, 2023
5354daf
Merge remote-tracking branch 'davis/exceptions-ttl-expire' into excep…
dplumlee Jan 4, 2023
823625d
fix types
dplumlee Jan 4, 2023
ec62825
fix types
dplumlee Jan 4, 2023
6f606f3
adds todo comment for future export options
dplumlee Jan 5, 2023
dc2f8fc
updates functional tests
dplumlee Jan 5, 2023
7d81489
updates tests
dplumlee Jan 9, 2023
80151bb
Merge remote-tracking branch 'upstream/main' into exceptions-ttl-expire
dplumlee Jan 9, 2023
dabcb11
updates tests
dplumlee Jan 9, 2023
9a7ff50
Merge remote-tracking branch 'upstream/main' into exceptions-ttl-expire
dplumlee Jan 17, 2023
c430e22
fix bugs and update tests
dplumlee Jan 18, 2023
643858a
addresses comments
dplumlee Jan 25, 2023
c18d3bf
Merge remote-tracking branch 'upstream/main' into exceptions-ttl-expire
dplumlee Jan 25, 2023
c60598a
Merge remote-tracking branch 'upstream/main' into exceptions-ttl-expire
dplumlee Jan 25, 2023
c48ba8c
adds tests
dplumlee Jan 27, 2023
319cb0d
fix typo
dplumlee Jan 27, 2023
8dc35c5
fixes tests
dplumlee Jan 31, 2023
d7ed598
Merge remote-tracking branch 'upstream/main' into exceptions-ttl-expire
dplumlee Jan 31, 2023
58d2c89
fix tests
dplumlee Jan 31, 2023
740be6e
Merge remote-tracking branch 'upstream/main' into exceptions-ttl-expire
dplumlee Feb 6, 2023
5fd3e63
adds integration test
dplumlee Feb 7, 2023
ce349e5
updates types
dplumlee Feb 7, 2023
c883c2c
updates css
dplumlee Feb 7, 2023
8105531
Merge remote-tracking branch 'upstream/main' into exceptions-ttl-expire
dplumlee Feb 7, 2023
565d753
fixes merge conflicts
dplumlee Feb 7, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import * as i18n from '../../translations';
interface MetaInfoDetailsProps {
label: string;
lastUpdate: JSX.Element | string;
lastUpdateValue: string;
lastUpdateValue?: string;
dataTestSubj?: string;
}

Expand All @@ -42,20 +42,24 @@ export const MetaInfoDetails = memo<MetaInfoDetailsProps>(
{lastUpdate}
</EuiBadge>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" css={euiBadgeFontFamily}>
{i18n.EXCEPTION_ITEM_CARD_META_BY}
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false} data-test-subj={`${dataTestSubj || ''}lastUpdateValue`}>
<EuiFlexGroup responsive gutterSize="xs" alignItems="center">
{lastUpdateValue != null && (
<>
<EuiFlexItem grow={false}>
<EuiBadge color="hollow" css={euiBadgeFontFamily}>
{lastUpdateValue}
</EuiBadge>
<EuiText size="xs" css={euiBadgeFontFamily}>
{i18n.EXCEPTION_ITEM_CARD_META_BY}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false} data-test-subj={`${dataTestSubj || ''}lastUpdateValue`}>
<EuiFlexGroup responsive gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<EuiBadge color="hollow" css={euiBadgeFontFamily}>
{lastUpdateValue}
</EuiBadge>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</>
)}
</EuiFlexGroup>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ export const ExceptionItemCardMetaInfo = memo<ExceptionItemCardMetaInfoProps>(
}),
[dataTestSubj, rules, securityLinkAnchorComponent]
);

const isExpired = useMemo(
() => (item.expire_time ? new Date(item.expire_time) <= new Date() : false),
[item]
);

return (
<EuiFlexGroup alignItems="center" responsive gutterSize="s" data-test-subj={dataTestSubj}>
{FormattedDateComponent !== null && (
Expand Down Expand Up @@ -77,6 +83,27 @@ export const ExceptionItemCardMetaInfo = memo<ExceptionItemCardMetaInfoProps>(
dataTestSubj={`${dataTestSubj || ''}UpdatedBy`}
/>
</EuiFlexItem>
{item.expire_time != null && (
<>
<EuiFlexItem css={itemCss} grow={false}>
<MetaInfoDetails
label={
isExpired
? i18n.EXCEPTION_ITEM_CARD_EXPIRED_LABEL
: i18n.EXCEPTION_ITEM_CARD_EXPIRES_LABEL
}
lastUpdate={
<FormattedDateComponent
data-test-subj={`{dataTestSubj||''}formattedDateComponentExpireTime`}
fieldName="expire_time"
value={item.expire_time}
/>
}
dataTestSubj={`${dataTestSubj || ''}ExpireTime`}
/>
</EuiFlexItem>
</>
)}
</>
)}
<EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ export const EXCEPTION_ITEM_CARD_UPDATED_LABEL = i18n.translate(
}
);

export const EXCEPTION_ITEM_CARD_EXPIRES_LABEL = i18n.translate(
'exceptionList-components.exceptions.exceptionItem.card.expiresLabel',
{
defaultMessage: 'Expires at',
}
);

export const EXCEPTION_ITEM_CARD_EXPIRED_LABEL = i18n.translate(
'exceptionList-components.exceptions.exceptionItem.card.expiredLabel',
{
defaultMessage: 'Expired at',
}
);

export const EXCEPTION_ITEM_CARD_META_BY = i18n.translate(
'exceptionList-components.exceptions.exceptionItem.card.metaDetailsBy',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ interface ExceptionListHeaderComponentProps {
canUserEditList?: boolean;
securityLinkAnchorComponent: React.ElementType; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common
onEditListDetails: (listDetails: ListDetails) => void;
onExportList: () => void;
onDeleteList: () => void;
onManageRules: () => void;
onExportList: () => void;
}

export interface BackOptions {
Expand All @@ -51,9 +51,9 @@ const ExceptionListHeaderComponent: FC<ExceptionListHeaderComponentProps> = ({
backOptions,
canUserEditList = true,
onEditListDetails,
onExportList,
onDeleteList,
onManageRules,
onExportList,
}) => {
const { isModalVisible, listDetails, onEdit, onSave, onCancel } = useExceptionListHeader({
name,
Expand Down Expand Up @@ -97,9 +97,9 @@ const ExceptionListHeaderComponent: FC<ExceptionListHeaderComponentProps> = ({
isReadonly={isReadonly}
canUserEditList={canUserEditList}
securityLinkAnchorComponent={securityLinkAnchorComponent}
onExportList={onExportList}
onDeleteList={onDeleteList}
onManageRules={onManageRules}
onExportList={onExportList}
/>,
]}
breadcrumbs={[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ interface MenuItemsProps {
linkedRules: Rule[];
canUserEditList?: boolean;
securityLinkAnchorComponent: React.ElementType; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common
onExportList: () => void;
onDeleteList: () => void;
onManageRules: () => void;
onExportList: () => void;
}

const MenuItemsComponent: FC<MenuItemsProps> = ({
Expand All @@ -29,9 +29,9 @@ const MenuItemsComponent: FC<MenuItemsProps> = ({
securityLinkAnchorComponent,
isReadonly,
canUserEditList = true,
onExportList,
onDeleteList,
onManageRules,
onExportList,
}) => {
const referencedLinks = useMemo(
() =>
Expand Down Expand Up @@ -78,7 +78,7 @@ const MenuItemsComponent: FC<MenuItemsProps> = ({
data-test-subj={`${dataTestSubj || ''}ManageRulesButton`}
fill
onClick={() => {
if (typeof onExportList === 'function') onManageRules();
if (typeof onManageRules === 'function') onManageRules();
}}
>
{i18n.EXCEPTION_LIST_HEADER_MANAGE_RULES_BUTTON}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ describe('MenuItems', () => {
fireEvent.click(wrapper.getByTestId('ManageRulesButton'));
expect(onManageRules).toHaveBeenCalled();
});
it('should call onExportList', () => {
it('should call onExportModalOpen', () => {
const wrapper = render(
<MenuItems
isReadonly={false}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const getExceptionListItemSchemaMock = (
},
{ field: 'some.not.nested.field', operator: 'included', type: 'match', value: 'some value' },
],
expire_time: undefined,
id: '1',
item_id: 'endpoint_list_item',
list_id: 'endpoint_list_id',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import * as t from 'io-ts';
import { IsoDateString } from '@kbn/securitysolution-io-ts-types';

export const expireTime = IsoDateString;
export const expireTimeOrUndefined = t.union([expireTime, t.undefined]);
export type ExpireTimeOrUndefined = t.TypeOf<typeof expireTimeOrUndefined>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

/* eslint-disable @typescript-eslint/naming-convention */

import * as t from 'io-ts';

export const include_expired_exceptions = t.keyof({ true: null, false: null });
export const includeExpiredExceptionsOrUndefined = t.union([
include_expired_exceptions,
t.undefined,
]);
export type IncludeExpiredExceptionsOrUndefined = t.TypeOf<
typeof includeExpiredExceptionsOrUndefined
>;
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export * from './entry_nested';
export * from './exception_export_details';
export * from './exception_list';
export * from './exception_list_item_type';
export * from './expire_time';
export * from './filter';
export * from './id';
export * from './immutable';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { description } from '../../common/description';
import { name } from '../../common/name';
import { meta } from '../../common/meta';
import { tags } from '../../common/tags';
import { ExpireTimeOrUndefined, expireTimeOrUndefined } from '../../common';

export const createEndpointListItemSchema = t.intersection([
t.exact(
Expand All @@ -39,6 +40,7 @@ export const createEndpointListItemSchema = t.intersection([
meta, // defaults to undefined if not set during decode
os_types: osTypeArrayOrUndefined, // defaults to empty array if not set during decode
tags, // defaults to empty array if not set during decode
expire_time: expireTimeOrUndefined, // defaults to undefined if not set during decode
})
),
]);
Expand All @@ -48,11 +50,12 @@ export type CreateEndpointListItemSchema = t.OutputOf<typeof createEndpointListI
// This type is used after a decode since some things are defaults after a decode.
export type CreateEndpointListItemSchemaDecoded = Omit<
RequiredKeepUndefined<t.TypeOf<typeof createEndpointListItemSchema>>,
'tags' | 'item_id' | 'entries' | 'comments' | 'os_types'
'tags' | 'item_id' | 'entries' | 'comments' | 'os_types' | 'expire_time'
> & {
comments: CreateCommentsArray;
tags: Tags;
item_id: ItemId;
entries: EntriesArray;
os_types: OsTypeArray;
expire_time: ExpireTimeOrUndefined;
};
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { meta } from '../../common/meta';
import { namespace_type } from '../../common/namespace_type';
import { tags } from '../../common/tags';
import { nonEmptyEntriesArray } from '../../common/non_empty_entries_array';
import { ExpireTimeOrUndefined, expireTimeOrUndefined } from '../../common';

export const createExceptionListItemSchema = t.intersection([
t.exact(
Expand All @@ -39,6 +40,7 @@ export const createExceptionListItemSchema = t.intersection([
t.exact(
t.partial({
comments: DefaultCreateCommentsArray, // defaults to empty array if not set during decode
expire_time: expireTimeOrUndefined,
item_id: DefaultUuid, // defaults to GUID (uuid v4) if not set during decode
meta, // defaults to undefined if not set during decode
namespace_type, // defaults to 'single' if not set during decode
Expand All @@ -53,9 +55,10 @@ export type CreateExceptionListItemSchema = t.OutputOf<typeof createExceptionLis
// This type is used after a decode since some things are defaults after a decode.
export type CreateExceptionListItemSchemaDecoded = Omit<
RequiredKeepUndefined<t.TypeOf<typeof createExceptionListItemSchema>>,
'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments'
'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments' | 'expire_time'
> & {
comments: CreateCommentsArray;
expire_time: ExpireTimeOrUndefined;
tags: Tags;
item_id: ItemId;
entries: EntriesArray;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
Tags,
tags,
name,
ExpireTimeOrUndefined,
expireTimeOrUndefined,
} from '../../common';
import { RequiredKeepUndefined } from '../../common/required_keep_undefined';

Expand All @@ -46,6 +48,7 @@ export const createRuleExceptionListItemSchema = t.intersection([
namespace_type: namespaceType, // defaults to 'single' if not set during decode
os_types: osTypeArrayOrUndefined, // defaults to empty array if not set during decode
tags, // defaults to empty array if not set during decode
expire_time: expireTimeOrUndefined,
})
),
]);
Expand All @@ -57,12 +60,13 @@ export type CreateRuleExceptionListItemSchema = t.OutputOf<
// This type is used after a decode since some things are defaults after a decode.
export type CreateRuleExceptionListItemSchemaDecoded = Omit<
RequiredKeepUndefined<t.TypeOf<typeof createRuleExceptionListItemSchema>>,
'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments'
'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments' | 'expire_time'
> & {
comments: CreateCommentsArray;
tags: Tags;
item_id: ItemId;
entries: EntriesArray;
namespace_type: NamespaceType;
os_types: OsTypeArray;
expire_time: ExpireTimeOrUndefined;
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export const getExportExceptionListQuerySchemaMock = (): ExportExceptionListQuer
id: ID,
list_id: LIST_ID,
namespace_type: NAMESPACE_TYPE,
include_expired_exceptions: 'true',
});
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ describe('export_exception_list_schema', () => {

expect(message.schema).toEqual({
id: 'uuid_here',
include_expired_exceptions: 'true',
list_id: 'some-list-id',
namespace_type: 'single',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import * as t from 'io-ts';

import { id } from '../../common/id';
import { includeExpiredExceptionsOrUndefined } from '../../common/include_expired_exceptions';
import { list_id } from '../../common/list_id';
import { namespace_type } from '../../common/namespace_type';

Expand All @@ -17,6 +18,7 @@ export const exportExceptionListQuerySchema = t.exact(
id,
list_id,
namespace_type,
include_expired_exceptions: includeExpiredExceptionsOrUndefined,
// TODO: Add file_name here with a default value
})
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ export const getImportExceptionsListItemSchemaDecodedMock = (
namespace_type: 'single',
os_types: [],
tags: [],
expire_time: undefined,
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { exceptionListItemType } from '../../common/exception_list_item_type';
import { ItemId } from '../../common/item_id';
import { EntriesArray } from '../../common/entries';
import { DefaultImportCommentsArray } from '../../common/default_import_comments_array';
import { ImportCommentsArray } from '../../common';
import { ExpireTimeOrUndefined, expireTimeOrUndefined, ImportCommentsArray } from '../../common';

/**
* Differences from this and the createExceptionsListItemSchema are
Expand Down Expand Up @@ -67,6 +67,7 @@ export const importExceptionListItemSchema = t.intersection([
namespace_type, // defaults to 'single' if not set during decode
os_types: osTypeArrayOrUndefined, // defaults to empty array if not set during decode
tags, // defaults to empty array if not set during decode
expire_time: expireTimeOrUndefined,
})
),
]);
Expand All @@ -76,12 +77,13 @@ export type ImportExceptionListItemSchema = t.OutputOf<typeof importExceptionLis
// This type is used after a decode since some things are defaults after a decode.
export type ImportExceptionListItemSchemaDecoded = Omit<
ImportExceptionListItemSchema,
'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments'
'tags' | 'item_id' | 'entries' | 'namespace_type' | 'comments' | 'expire_time'
> & {
comments: ImportCommentsArray;
tags: Tags;
item_id: ItemId;
entries: EntriesArray;
namespace_type: NamespaceType;
os_types: OsTypeArray;
expire_time: ExpireTimeOrUndefined;
};
Loading