Suricata Row Renders Upgrade (#31990)

* Fixed `event.severity` to be operational now that ECS has been updated
* Added a Suricata SID database with references
* Wired in the references
* Created a tagging system for the UI
* Added Suricata SID and made it draggable onto the filters of the timeline
* Changed the hand rolled sushi 🍣 inline flex styles to EuiFlexItem, EuiGroupItem
* Removed older CVE column renderer from the system
* Wrote unit tests
* https://github.com/elastic/ingest-dev/issues/175
* https://github.com/elastic/ingest-dev/issues/299
This commit is contained in:
Frank Hassanabad 2019-02-27 22:10:23 -07:00 committed by GitHub
parent e9c1360dab
commit 90fd992c09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 7326 additions and 468 deletions

View file

@ -58,6 +58,7 @@
"x-pack/plugins/secops/public/components/flyout/index.tsx",
"x-pack/plugins/secops/public/components/flyout/resize_handle.tsx",
"x-pack/plugins/secops/public/components/notes/helpers.tsx",
"x-pack/plugins/secops/public/components/timeline/body/renderers/suricata_rules_ref.ts",
"x-pack/plugins/secops/public/components/timeline/properties/helpers.tsx",
"x-pack/plugins/secops/public/components/timeline/properties/index.tsx",
"x-pack/plugins/secops/public/components/timeline/search_or_filter/search_or_filter.tsx",

View file

@ -17,6 +17,7 @@
"react-markdown": "^4.0.6",
"lodash": "^4.17.10",
"memoize-one": "^5.0.0",
"apollo-link-error": "^1.1.7"
"apollo-link-error": "^1.1.7",
"suricata-sid-db": "^1.0.2"
}
}

View file

@ -79,10 +79,6 @@ exports[`Columns it renders the expected columns 1`] = `
"isInstance": [Function],
"renderColumn": [Function],
},
Object {
"isInstance": [Function],
"renderColumn": [Function],
},
]
}
ecs={
@ -127,7 +123,7 @@ exports[`Columns it renders the expected columns 1`] = `
>
<EuiFlexGroup
alignItems="stretch"
className="sc-jtRfpW erLKZH"
className="sc-gxMtzJ gYJQDW"
component="div"
data-test-subj="data-driven-columns"
direction="row"
@ -137,7 +133,7 @@ exports[`Columns it renders the expected columns 1`] = `
wrap={false}
>
<div
className="euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive sc-jtRfpW erLKZH"
className="euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive sc-gxMtzJ gYJQDW"
data-test-subj="data-driven-columns"
>
<EuiFlexItem
@ -155,7 +151,7 @@ exports[`Columns it renders the expected columns 1`] = `
minWidth="120px"
>
<div
className="sc-kTUwUJ hUSEpG"
className="sc-dfVpRl kAmxmB"
data-test-subj="column"
>
<Connect(DraggableWrapperComponent)
@ -581,7 +577,7 @@ exports[`Columns it renders the expected columns 1`] = `
minWidth="120px"
>
<div
className="sc-kTUwUJ gSTrkX"
className="sc-dfVpRl gLUJqQ"
data-test-subj="column"
>
<Connect(DraggableWrapperComponent)
@ -1007,7 +1003,7 @@ exports[`Columns it renders the expected columns 1`] = `
minWidth="120px"
>
<div
className="sc-kTUwUJ hUSEpG"
className="sc-dfVpRl kAmxmB"
data-test-subj="column"
>
<Connect(DraggableWrapperComponent)
@ -1433,7 +1429,7 @@ exports[`Columns it renders the expected columns 1`] = `
minWidth="120px"
>
<div
className="sc-kTUwUJ gSTrkX"
className="sc-dfVpRl gLUJqQ"
data-test-subj="column"
>
<Connect(DraggableWrapperComponent)
@ -1859,7 +1855,7 @@ exports[`Columns it renders the expected columns 1`] = `
minWidth="120px"
>
<div
className="sc-kTUwUJ hUSEpG"
className="sc-dfVpRl kAmxmB"
data-test-subj="column"
>
<Connect(DraggableWrapperComponent)
@ -2285,7 +2281,7 @@ exports[`Columns it renders the expected columns 1`] = `
minWidth="120px"
>
<div
className="sc-kTUwUJ gSTrkX"
className="sc-dfVpRl gLUJqQ"
data-test-subj="column"
>
<Connect(DraggableWrapperComponent)
@ -2711,7 +2707,7 @@ exports[`Columns it renders the expected columns 1`] = `
minWidth="120px"
>
<div
className="sc-kTUwUJ hUSEpG"
className="sc-dfVpRl kAmxmB"
data-test-subj="column"
>
<Connect(DraggableWrapperComponent)

View file

@ -0,0 +1,721 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SuricataSignature rendering it renders the default SuricataSignature 1`] = `
<ThemeProvider
intl={
Object {
"defaultFormats": Object {},
"defaultLocale": "en",
"formatDate": [Function],
"formatHTMLMessage": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatPlural": [Function],
"formatRelative": [Function],
"formatTime": [Function],
"formats": Object {
"date": Object {
"full": Object {
"day": "numeric",
"month": "long",
"weekday": "long",
"year": "numeric",
},
"long": Object {
"day": "numeric",
"month": "long",
"year": "numeric",
},
"medium": Object {
"day": "numeric",
"month": "short",
"year": "numeric",
},
"short": Object {
"day": "numeric",
"month": "numeric",
"year": "2-digit",
},
},
"number": Object {
"currency": Object {
"style": "currency",
},
"percent": Object {
"style": "percent",
},
},
"relative": Object {
"days": Object {
"units": "day",
},
"hours": Object {
"units": "hour",
},
"minutes": Object {
"units": "minute",
},
"months": Object {
"units": "month",
},
"seconds": Object {
"units": "second",
},
"years": Object {
"units": "year",
},
},
"time": Object {
"full": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"long": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"medium": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
},
"short": Object {
"hour": "numeric",
"minute": "numeric",
},
},
},
"formatters": Object {
"getDateTimeFormat": [Function],
"getMessageFormat": [Function],
"getNumberFormat": [Function],
"getPluralFormat": [Function],
"getRelativeFormat": [Function],
},
"locale": "en",
"messages": Object {},
"now": [Function],
"onError": [Function],
"textComponent": Symbol(react.fragment),
"timeZone": null,
}
}
theme={[Function]}
>
<Provider
store={
Object {
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(observable): [Function],
}
}
>
<DragDropContext
onDragEnd={[Function]}
>
<pure(Component)
id="doc-id-123"
signature="ET SCAN ATTACK Hello"
signatureId="id-123"
>
<Component
id="doc-id-123"
signature="ET SCAN ATTACK Hello"
signatureId="id-123"
>
<EuiFlexGroup
alignItems="stretch"
component="div"
direction="row"
gutterSize="none"
justifyContent="center"
responsive={true}
wrap={false}
>
<div
className="euiFlexGroup euiFlexGroup--justifyContentCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
>
<pure(Component)
id="doc-id-123"
signatureId="id-123"
>
<Component
id="doc-id-123"
signatureId="id-123"
>
<Styled(EuiFlexItem)
grow={false}
>
<EuiFlexItem
className="sc-jwKygS jynuQh"
component="div"
grow={false}
>
<div
className="euiFlexItem euiFlexItem--flexGrowZero sc-jwKygS jynuQh"
>
<Connect(DraggableWrapperComponent)
dataProvider={
Object {
"and": Array [],
"enabled": true,
"excluded": false,
"id": "suricata-doc-id-123-sig-id-123",
"kqlQuery": "",
"name": "id-123",
"queryMatch": Object {
"field": "suricata.eve.alert.signature_id",
"value": "id-123",
},
}
}
render={[Function]}
>
<DraggableWrapperComponent
dataProvider={
Object {
"and": Array [],
"enabled": true,
"excluded": false,
"id": "suricata-doc-id-123-sig-id-123",
"kqlQuery": "",
"name": "id-123",
"queryMatch": Object {
"field": "suricata.eve.alert.signature_id",
"value": "id-123",
},
}
}
registerProvider={[Function]}
render={[Function]}
suricata-doc-id-123-sig-id-123={
Object {
"and": Array [],
"enabled": true,
"excluded": false,
"id": "suricata-doc-id-123-sig-id-123",
"kqlQuery": "",
"name": "id-123",
"queryMatch": Object {
"field": "suricata.eve.alert.signature_id",
"value": "id-123",
},
}
}
unRegisterProvider={[Function]}
>
<div
data-test-subj="draggableWrapperDiv"
>
<Connect(Droppable)
direction="vertical"
droppableId="droppableId.content.suricata-doc-id-123-sig-id-123"
ignoreContainerClipping={false}
isDropDisabled={true}
type="DEFAULT"
>
<Droppable
direction="vertical"
dispatch={[Function]}
draggingOverWith={null}
droppableId="droppableId.content.suricata-doc-id-123-sig-id-123"
ignoreContainerClipping={false}
isDraggingOver={false}
isDropDisabled={true}
placeholder={null}
type="DEFAULT"
>
<DroppableDimensionPublisher
direction="vertical"
droppableId="droppableId.content.suricata-doc-id-123-sig-id-123"
getDroppableRef={[Function]}
ignoreContainerClipping={false}
isDropDisabled={true}
type="DEFAULT"
>
<div
data-react-beautiful-dnd-droppable="0"
>
<Connect(Draggable)
disableInteractiveElementBlocking={false}
draggableId="draggableId.content.suricata-doc-id-123-sig-id-123"
index={0}
isDragDisabled={false}
key="suricata-doc-id-123-sig-id-123"
>
<Draggable
dimension={null}
disableInteractiveElementBlocking={false}
draggableId="draggableId.content.suricata-doc-id-123-sig-id-123"
draggingOver={null}
drop={[Function]}
dropAnimationFinished={[Function]}
index={0}
isDragDisabled={false}
isDragging={false}
isDropAnimating={false}
lift={[Function]}
move={[Function]}
moveByWindowScroll={[Function]}
moveDown={[Function]}
moveLeft={[Function]}
moveRight={[Function]}
moveUp={[Function]}
offset={
Object {
"x": 0,
"y": 0,
}
}
shouldAnimateDisplacement={true}
shouldAnimateDragMovement={false}
>
<DraggableDimensionPublisher
draggableId="draggableId.content.suricata-doc-id-123-sig-id-123"
droppableId="droppableId.content.suricata-doc-id-123-sig-id-123"
getDraggableRef={[Function]}
index={0}
key="draggableId.content.suricata-doc-id-123-sig-id-123"
type="DEFAULT"
>
<Moveable
destination={
Object {
"x": 0,
"y": 0,
}
}
onMoveEnd={[Function]}
speed="INSTANT"
>
<Motion
defaultStyle={
Object {
"x": 0,
"y": 0,
}
}
onRest={[Function]}
style={
Object {
"x": 0,
"y": 0,
}
}
>
<DoubleRenderBlocker
change={
Object {
"x": 0,
"y": 0,
}
}
>
<DragHandle
callbacks={
Object {
"onCancel": [Function],
"onDrop": [Function],
"onLift": [Function],
"onMove": [Function],
"onMoveDown": [Function],
"onMoveLeft": [Function],
"onMoveRight": [Function],
"onMoveUp": [Function],
"onWindowScroll": [Function],
}
}
canDragInteractiveElements={false}
draggableId="draggableId.content.suricata-doc-id-123-sig-id-123"
getDraggableRef={[Function]}
isDragging={false}
isDropAnimating={false}
isEnabled={true}
>
<styled.div
aria-roledescription="Draggable item. Press space bar to lift"
data-react-beautiful-dnd-drag-handle="0"
data-react-beautiful-dnd-draggable="0"
data-test-subj="providerContainer"
draggable={false}
innerRef={[Function]}
onBlur={[Function]}
onDragStart={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onTouchStart={[Function]}
style={
Object {
"transform": null,
"transition": null,
"zIndex": 9000,
}
}
tabIndex={0}
>
<div
aria-roledescription="Draggable item. Press space bar to lift"
className="sc-hzDkRC koNYSi"
data-react-beautiful-dnd-drag-handle="0"
data-react-beautiful-dnd-draggable="0"
data-test-subj="providerContainer"
draggable={false}
onBlur={[Function]}
onDragStart={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onTouchStart={[Function]}
style={
Object {
"transform": null,
"transition": null,
"zIndex": 9000,
}
}
tabIndex={0}
>
<Styled(EuiBadge)
color="hollow"
iconType="number"
>
<EuiBadge
className="sc-btzYZH jBzfTi"
color="hollow"
iconSide="left"
iconType="number"
>
<span
className="euiBadge euiBadge--hollow sc-btzYZH jBzfTi"
style={null}
>
<span
className="euiBadge__content"
>
<EuiIcon
className="euiBadge__icon"
size="s"
type="number"
>
<number
className="euiIcon euiIcon--small euiBadge__icon"
focusable="false"
height="16"
style={null}
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<svg
className="euiIcon euiIcon--small euiBadge__icon"
focusable="false"
height="16"
style={null}
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.808 10.197H6.796L5.859 13H4.485l.937-2.803H3.966l.219-1.25h1.647l.608-1.805H4.991l.226-1.251h1.64l.95-2.844h1.368l-.95 2.844h1.018l.95-2.844h1.374l-.95 2.844h1.51l-.218 1.25h-1.702l-.608 1.805h1.497l-.219 1.251H9.182L8.252 13H6.878l.93-2.803zm-.602-1.25h1.012l.615-1.805H7.814l-.608 1.804z"
fillRule="evenodd"
/>
</svg>
</number>
</EuiIcon>
<span
className="euiBadge__text"
>
id-123
</span>
</span>
</span>
</EuiBadge>
</Styled(EuiBadge)>
</div>
</styled.div>
</DragHandle>
</DoubleRenderBlocker>
</Motion>
</Moveable>
</DraggableDimensionPublisher>
</Draggable>
</Connect(Draggable)>
</div>
</DroppableDimensionPublisher>
</Droppable>
</Connect(Droppable)>
</div>
</DraggableWrapperComponent>
</Connect(DraggableWrapperComponent)>
</div>
</EuiFlexItem>
</Styled(EuiFlexItem)>
</Component>
</pure(Component)>
<pure(Component)
tokens={
Array [
"ET",
"SCAN",
"ATTACK",
]
}
>
<Component
tokens={
Array [
"ET",
"SCAN",
"ATTACK",
]
}
>
<Styled(EuiFlexItem)
grow={false}
key="ET"
>
<EuiFlexItem
className="sc-lhVmIH krgDqk"
component="div"
grow={false}
>
<div
className="euiFlexItem euiFlexItem--flexGrowZero sc-lhVmIH krgDqk"
>
<EuiBadge
color="hollow"
iconSide="left"
iconType="tag"
>
<span
className="euiBadge euiBadge--hollow"
style={null}
>
<span
className="euiBadge__content"
>
<EuiIcon
className="euiBadge__icon"
size="s"
type="tag"
>
<tag
className="euiIcon euiIcon--small euiBadge__icon"
focusable="false"
height="16"
style={null}
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<svg
className="euiIcon euiIcon--small euiBadge__icon"
focusable="false"
height="16"
style={null}
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.254 14.97L.996 9.712c-.315-.316-.397-.463-.45-.64a.909.909 0 0 1 0-.534c.053-.177.135-.324.45-.64L7.43 1.466c.182-.183.252-.24.338-.293a.87.87 0 0 1 .273-.113c.099-.023.188-.032.446-.032h5.173c.445 0 .607.046.77.133.162.087.29.214.377.377.088.162.134.324.136.769l.015 5.15c0 .259-.009.348-.032.448a.87.87 0 0 1-.112.273c-.054.087-.111.157-.294.34L8.067 14.97c-.315.315-.462.396-.639.45a.909.909 0 0 1-.535 0c-.176-.054-.324-.135-.639-.45zm1.106-.707l6.453-6.453c.092-.092.126-.128.141-.147.003-.025.004-.074.004-.204l-.015-5.15c0-.181-.003-.245-.009-.275a2.247 2.247 0 0 0-.274-.007H8.487c-.13 0-.179.001-.203.004-.02.015-.055.05-.147.141L1.703 8.606a2.248 2.248 0 0 0-.189.2c.017.024.061.07.19.198l5.257 5.259c.128.128.175.171.2.188.024-.017.071-.06.2-.188zm4.972-10.607a2 2 0 1 1-2.828 2.828 2 2 0 0 1 2.828-2.828zm-.707.707a1 1 0 1 0-1.414 1.414 1 1 0 0 0 1.414-1.414zM6.807 11.28L4.686 9.159a.5.5 0 1 1 .707-.707l2.121 2.12a.5.5 0 1 1-.707.708zm1.414-1.414l-2.12-2.122a.5.5 0 1 1 .706-.707L8.928 9.16a.5.5 0 1 1-.707.707z"
/>
</svg>
</tag>
</EuiIcon>
<span
className="euiBadge__text"
>
ET
</span>
</span>
</span>
</EuiBadge>
</div>
</EuiFlexItem>
</Styled(EuiFlexItem)>
<Styled(EuiFlexItem)
grow={false}
key="SCAN"
>
<EuiFlexItem
className="sc-lhVmIH krgDqk"
component="div"
grow={false}
>
<div
className="euiFlexItem euiFlexItem--flexGrowZero sc-lhVmIH krgDqk"
>
<EuiBadge
color="hollow"
iconSide="left"
iconType="tag"
>
<span
className="euiBadge euiBadge--hollow"
style={null}
>
<span
className="euiBadge__content"
>
<EuiIcon
className="euiBadge__icon"
size="s"
type="tag"
>
<tag
className="euiIcon euiIcon--small euiBadge__icon"
focusable="false"
height="16"
style={null}
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<svg
className="euiIcon euiIcon--small euiBadge__icon"
focusable="false"
height="16"
style={null}
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.254 14.97L.996 9.712c-.315-.316-.397-.463-.45-.64a.909.909 0 0 1 0-.534c.053-.177.135-.324.45-.64L7.43 1.466c.182-.183.252-.24.338-.293a.87.87 0 0 1 .273-.113c.099-.023.188-.032.446-.032h5.173c.445 0 .607.046.77.133.162.087.29.214.377.377.088.162.134.324.136.769l.015 5.15c0 .259-.009.348-.032.448a.87.87 0 0 1-.112.273c-.054.087-.111.157-.294.34L8.067 14.97c-.315.315-.462.396-.639.45a.909.909 0 0 1-.535 0c-.176-.054-.324-.135-.639-.45zm1.106-.707l6.453-6.453c.092-.092.126-.128.141-.147.003-.025.004-.074.004-.204l-.015-5.15c0-.181-.003-.245-.009-.275a2.247 2.247 0 0 0-.274-.007H8.487c-.13 0-.179.001-.203.004-.02.015-.055.05-.147.141L1.703 8.606a2.248 2.248 0 0 0-.189.2c.017.024.061.07.19.198l5.257 5.259c.128.128.175.171.2.188.024-.017.071-.06.2-.188zm4.972-10.607a2 2 0 1 1-2.828 2.828 2 2 0 0 1 2.828-2.828zm-.707.707a1 1 0 1 0-1.414 1.414 1 1 0 0 0 1.414-1.414zM6.807 11.28L4.686 9.159a.5.5 0 1 1 .707-.707l2.121 2.12a.5.5 0 1 1-.707.708zm1.414-1.414l-2.12-2.122a.5.5 0 1 1 .706-.707L8.928 9.16a.5.5 0 1 1-.707.707z"
/>
</svg>
</tag>
</EuiIcon>
<span
className="euiBadge__text"
>
SCAN
</span>
</span>
</span>
</EuiBadge>
</div>
</EuiFlexItem>
</Styled(EuiFlexItem)>
<Styled(EuiFlexItem)
grow={false}
key="ATTACK"
>
<EuiFlexItem
className="sc-lhVmIH krgDqk"
component="div"
grow={false}
>
<div
className="euiFlexItem euiFlexItem--flexGrowZero sc-lhVmIH krgDqk"
>
<EuiBadge
color="hollow"
iconSide="left"
iconType="tag"
>
<span
className="euiBadge euiBadge--hollow"
style={null}
>
<span
className="euiBadge__content"
>
<EuiIcon
className="euiBadge__icon"
size="s"
type="tag"
>
<tag
className="euiIcon euiIcon--small euiBadge__icon"
focusable="false"
height="16"
style={null}
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<svg
className="euiIcon euiIcon--small euiBadge__icon"
focusable="false"
height="16"
style={null}
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.254 14.97L.996 9.712c-.315-.316-.397-.463-.45-.64a.909.909 0 0 1 0-.534c.053-.177.135-.324.45-.64L7.43 1.466c.182-.183.252-.24.338-.293a.87.87 0 0 1 .273-.113c.099-.023.188-.032.446-.032h5.173c.445 0 .607.046.77.133.162.087.29.214.377.377.088.162.134.324.136.769l.015 5.15c0 .259-.009.348-.032.448a.87.87 0 0 1-.112.273c-.054.087-.111.157-.294.34L8.067 14.97c-.315.315-.462.396-.639.45a.909.909 0 0 1-.535 0c-.176-.054-.324-.135-.639-.45zm1.106-.707l6.453-6.453c.092-.092.126-.128.141-.147.003-.025.004-.074.004-.204l-.015-5.15c0-.181-.003-.245-.009-.275a2.247 2.247 0 0 0-.274-.007H8.487c-.13 0-.179.001-.203.004-.02.015-.055.05-.147.141L1.703 8.606a2.248 2.248 0 0 0-.189.2c.017.024.061.07.19.198l5.257 5.259c.128.128.175.171.2.188.024-.017.071-.06.2-.188zm4.972-10.607a2 2 0 1 1-2.828 2.828 2 2 0 0 1 2.828-2.828zm-.707.707a1 1 0 1 0-1.414 1.414 1 1 0 0 0 1.414-1.414zM6.807 11.28L4.686 9.159a.5.5 0 1 1 .707-.707l2.121 2.12a.5.5 0 1 1-.707.708zm1.414-1.414l-2.12-2.122a.5.5 0 1 1 .706-.707L8.928 9.16a.5.5 0 1 1-.707.707z"
/>
</svg>
</tag>
</EuiIcon>
<span
className="euiBadge__text"
>
ATTACK
</span>
</span>
</span>
</EuiBadge>
</div>
</EuiFlexItem>
</Styled(EuiFlexItem)>
</Component>
</pure(Component)>
<pure(Component)
link="ET SCAN ATTACK Hello"
value="Hello"
>
<Component
link="ET SCAN ATTACK Hello"
value="Hello"
>
<Styled(EuiFlexItem)
grow={false}
>
<EuiFlexItem
className="sc-bYSBpT kqevpY"
component="div"
grow={false}
>
<div
className="euiFlexItem euiFlexItem--flexGrowZero sc-bYSBpT kqevpY"
>
<EuiLink
color="primary"
href="https://www.google.com/search?q=ET%20SCAN%20ATTACK%20Hello"
target="_blank"
type="button"
>
<a
className="euiLink euiLink--primary"
href="https://www.google.com/search?q=ET%20SCAN%20ATTACK%20Hello"
rel="noopener noreferrer"
target="_blank"
>
Hello
</a>
</EuiLink>
</div>
</EuiFlexItem>
</Styled(EuiFlexItem)>
</Component>
</pure(Component)>
</div>
</EuiFlexGroup>
</Component>
</pure(Component)>
</DragDropContext>
</Provider>
</ThemeProvider>
`;

View file

@ -24,11 +24,9 @@ const allFieldsInSchemaByName = getAllFieldsInSchemaByMappedName(virtualEcsSchem
describe('get_column_renderer', () => {
let nonSuricata: Ecs;
let suricata: Ecs;
beforeEach(() => {
nonSuricata = cloneDeep(mockEcsData[0]);
suricata = cloneDeep(mockEcsData[2]);
});
test('should render event id when dealing with data that is not suricata', () => {
@ -52,18 +50,6 @@ describe('get_column_renderer', () => {
expect(wrapper.text()).toEqual('1');
});
test('should render CVE text as the event when dealing with a suricata event', () => {
const columnName = 'event.id';
const columnRenderer = getColumnRenderer(columnName, columnRenderers, suricata);
const column = columnRenderer.renderColumn(
columnName,
suricata,
allFieldsInSchemaByName[columnName]
);
const wrapper = mount(<span>{column}</span>);
expect(wrapper.text()).toEqual('CVE-2016-10174');
});
test('should render empty value when dealing with an empty value of user', () => {
delete nonSuricata.user;
const columnName = 'user.name';

View file

@ -19,9 +19,5 @@ export const getColumnRenderer = (
const renderer = columnRenderers.find(columnRenderer =>
columnRenderer.isInstance(columnName, ecs)
);
if (renderer == null) {
return unhandledColumnRenderer();
} else {
return renderer;
}
return renderer != null ? renderer : unhandledColumnRenderer();
};

View file

@ -58,7 +58,7 @@ describe('get_column_renderer', () => {
</ThemeProvider>
);
expect(wrapper.text()).toContain(
'some child ET EXPLOIT NETGEAR WNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)'
'some child 4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343'
);
});
});

View file

@ -1,104 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { createLinkWithSignature, getSuricataCVEFromSignature } from '.';
describe('index', () => {
describe('#getSuricataCVEFromSignature', () => {
test('should parse a basic CVE string by its self', () => {
const cve = getSuricataCVEFromSignature('CVE-123-123');
expect(cve).toEqual('CVE-123-123');
});
test('should parse a basic CVE string in the middle of a signature', () => {
const cve = getSuricataCVEFromSignature(
'I am a normal signature and this is CVE-123-123 for you'
);
expect(cve).toEqual('CVE-123-123');
});
test('should parse a basic CVE string at the beginning of a signature', () => {
const cve = getSuricataCVEFromSignature('CVE-123-123 I am a normal signature for you.');
expect(cve).toEqual('CVE-123-123');
});
test('should parse a basic CVE string at the end of a signature', () => {
const cve = getSuricataCVEFromSignature('I am a normal signature for you. CVE-123-123');
expect(cve).toEqual('CVE-123-123');
});
test('should parse a basic CVE string with no spaces mixed in a signature at the end', () => {
const cve = getSuricataCVEFromSignature('I am a normal signature for youCVE-123-123');
expect(cve).toEqual('CVE-123-123');
});
test('should parse a basic CVE string with no spaces mixed in a signature at the beginning', () => {
const cve = getSuricataCVEFromSignature('CVE-123-123I am a normal signature for you');
expect(cve).toEqual('CVE-123-123');
});
test('should parse a basic CVE string with no spaces mixed in the middle of a word', () => {
const cve = getSuricataCVEFromSignature('I am a normalCVE-123-123signature for you');
expect(cve).toEqual('CVE-123-123');
});
test('should return a null if a CVE is not present', () => {
const cve = getSuricataCVEFromSignature('I am a normal signature for you');
expect(cve).toBeNull();
});
test('should return a null if the signature is empty', () => {
const cve = getSuricataCVEFromSignature();
expect(cve).toBeNull();
});
});
describe('#createLinkWithSignature', () => {
test('should parse a basic CVE string by its self as a hyper link to cve.mitre.org', () => {
const cve = createLinkWithSignature('CVE-123-123');
expect(cve).toEqual('https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-123-123');
});
test('should parse a basic CVE string in the middle of a signature as a hyper link to cve.mitre.org', () => {
const cve = createLinkWithSignature(
'I am a normal signature and this is CVE-123-123 for you'
);
expect(cve).toEqual('https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-123-123');
});
test('should parse a basic CVE string at the beginning of a signature as a hyper link to cve.mitre.org', () => {
const cve = createLinkWithSignature('CVE-123-123 I am a normal signature for you.');
expect(cve).toEqual('https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-123-123');
});
test('should parse a basic CVE string at the end of a signature as a hyper link to cve.mitre.org', () => {
const cve = createLinkWithSignature('I am a normal signature for you. CVE-123-123');
expect(cve).toEqual('https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-123-123');
});
test('should parse a basic CVE string with no spaces mixed in a signature at the end as a hyper link to cve.mitre.org', () => {
const cve = createLinkWithSignature('I am a normal signature for youCVE-123-123');
expect(cve).toEqual('https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-123-123');
});
test('should parse a basic CVE string with no spaces mixed in a signature at the beginning as a hyper link to cve.mitre.org', () => {
const cve = createLinkWithSignature('CVE-123-123I am a normal signature for you');
expect(cve).toEqual('https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-123-123');
});
test('should parse a basic CVE string with no spaces mixed in the middle of a word as a hyper link to cve.mitre.org', () => {
const cve = createLinkWithSignature('I am a normalCVE-123-123signature for you');
expect(cve).toEqual('https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-123-123');
});
test('should parse a link to google.com with no CVE in the signature', () => {
const cve = createLinkWithSignature('I am a normal signature for you');
expect(cve).toEqual(
'https://www.google.com/search?q=I%20am%20a%20normal%20signature%20for%20you'
);
});
});
});

View file

@ -9,7 +9,6 @@ import { emptyColumnRenderer } from './empty_column_renderer';
import { plainColumnRenderer } from './plain_column_renderer';
import { plainRowRenderer } from './plain_row_renderer';
import { RowRenderer } from './row_renderer';
import { suricataColumnRenderer } from './suricata_column_renderer';
import { suricataRowRenderer } from './suricata_row_renderer';
import { unknownColumnRenderer } from './unknown_column_renderer';
@ -20,34 +19,13 @@ export * from './get_row_renderer';
export * from './get_column_renderer';
export * from './plain_row_renderer';
export * from './plain_column_renderer';
export * from './suricata_column_renderer';
export * from './suricata_row_renderer';
export * from './unknown_column_renderer';
export const rowRenderers: RowRenderer[] = [suricataRowRenderer, plainRowRenderer];
export const columnRenderers: ColumnRenderer[] = [
suricataColumnRenderer,
plainColumnRenderer,
emptyColumnRenderer,
unknownColumnRenderer,
];
export const getSuricataCVEFromSignature = (signature?: string): string | null => {
const regex = /CVE-[0-9]*-[0-9]*/;
const found = (signature && signature.match(regex)) || false;
if (found) {
return encodeURIComponent(found[0]);
} else {
return null;
}
};
export const createLinkWithSignature = (signature: string): string => {
const cve = getSuricataCVEFromSignature(signature);
if (cve != null) {
return `https://cve.mitre.org/cgi-bin/cvename.cgi?name=${cve}`;
} else {
return `https://www.google.com/search?q=${encodeURIComponent(signature)}`;
}
};

View file

@ -0,0 +1,44 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
import toJson from 'enzyme-to-json';
import { noop } from 'lodash/fp';
import * as React from 'react';
import { DragDropContext } from 'react-beautiful-dnd';
import { Provider } from 'react-redux';
import { ThemeProvider } from 'styled-components';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { mockEcsData } from '../../../../mock';
import { mockGlobalState } from '../../../../mock';
import { createStore, State } from '../../../../store';
import { SourceDest } from './source_dest_ip';
describe('SuricataDestIp', () => {
const state: State = mockGlobalState;
const theme = () => ({ eui: euiDarkVars, darkMode: true });
let store = createStore(state);
beforeEach(() => {
store = createStore(state);
});
describe('rendering', () => {
test('it renders the default SuricataDestIp', () => {
const wrapper = mountWithIntl(
<ThemeProvider theme={theme}>
<Provider store={store}>
<DragDropContext onDragEnd={noop}>
<SourceDest data={mockEcsData[2]} />
</DragDropContext>
</Provider>
</ThemeProvider>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
});
});

View file

@ -0,0 +1,139 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { getOr } from 'lodash/fp';
import * as React from 'react';
import { pure } from 'recompose';
import styled from 'styled-components';
import { Ecs } from '../../../../graphql/types';
import {
fieldExists,
getAllFieldsInSchemaByMappedName,
getMappedEcsValue,
mappedEcsSchemaFieldNames,
virtualEcsSchema,
} from '../../../../lib/ecs';
import { escapeQueryValue } from '../../../../lib/keury';
import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper';
import { escapeDataProviderId } from '../../../drag_and_drop/helpers';
import { Provider } from '../../data_providers/provider';
import { FormattedField } from './formatted_field';
import * as i18n from './translations';
const Label = styled.div`
font-weight: bold;
`;
const allFieldsInSchemaByName = getAllFieldsInSchemaByMappedName(virtualEcsSchema);
export const DraggableValue = pure<{ data: Ecs; fieldName: string }>(({ data, fieldName }) => {
const itemDataProvider = {
enabled: true,
id: escapeDataProviderId(`row-render-value-for-${data._id}-${fieldName}`),
name: `${fieldName}: ${getMappedEcsValue({
data,
fieldName,
})}`,
queryMatch: {
field: getOr(fieldName, fieldName, mappedEcsSchemaFieldNames),
value: escapeQueryValue(
getMappedEcsValue({
data,
fieldName,
})
),
},
excluded: false,
kqlQuery: '',
and: [],
};
const field = allFieldsInSchemaByName[fieldName];
const fieldType = field != null ? field.type : '';
return { data, fieldName } ? (
<DraggableWrapper
key={`row-render-value-for-${data._id}-${fieldName}`}
dataProvider={itemDataProvider}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (
<DragEffects>
<Provider dataProvider={dataProvider} />
</DragEffects>
) : (
<FormattedField data={data} fieldName={fieldName} fieldType={fieldType} />
)
}
/>
) : null;
});
export const SourceIp = pure(({ data }: { data: Ecs }) =>
fieldExists({ data, fieldName: 'source.ip' }) ? (
<>
<EuiFlexItem>
<Label>{i18n.SOURCE}</Label>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup gutterSize="none">
<EuiFlexItem grow={false}>
<DraggableValue
data={data}
data-test-subj="source-ip-and-port"
fieldName={'source.ip'}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>:</EuiFlexItem>
<EuiFlexItem grow={false}>
<DraggableValue data={data} fieldName={'source.port'} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</>
) : null
);
export const DestinationIp = pure(({ data }: { data: Ecs }) =>
fieldExists({ data, fieldName: 'destination.ip' }) ? (
<>
<EuiFlexItem>
<Label>{i18n.DESTINATION}</Label>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup gutterSize="none">
<EuiFlexItem grow={false}>
<DraggableValue
data={data}
data-test-subj="destination-ip-and-port"
fieldName={'destination.ip'}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>:</EuiFlexItem>
<EuiFlexItem grow={false}>
<DraggableValue data={data} fieldName={'destination.port'} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</>
) : null
);
export const SourceDest = pure(({ data }: { data: Ecs }) => (
<EuiFlexGroup justifyContent="spaceEvenly">
<EuiFlexItem grow={false}>
<EuiFlexGroup direction="column" alignItems="center" gutterSize="none">
<SourceIp data={data} />
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup direction="column" alignItems="center" gutterSize="none">
<DestinationIp data={data} />
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
));

View file

@ -1,95 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { mount } from 'enzyme';
import { cloneDeep, set } from 'lodash/fp';
import React from 'react';
import { suricataColumnRenderer } from '.';
import { Ecs } from '../../../../graphql/types';
import { getAllFieldsInSchemaByMappedName, virtualEcsSchema } from '../../../../lib/ecs';
import { mockEcsData } from '../../../../mock';
import { getEmptyValue } from '../../../empty_value';
const allFieldsInSchemaByName = getAllFieldsInSchemaByMappedName(virtualEcsSchema);
describe('suricata_column_renderer', () => {
let mockDatum: Ecs;
beforeEach(() => {
mockDatum = cloneDeep(mockEcsData[2]);
});
test('should return isInstance of false if event is empty', () => {
delete mockDatum.event;
expect(suricataColumnRenderer.isInstance('event.id', mockDatum)).toBe(false);
});
test('should return isInstance of false if event module is empty', () => {
delete mockDatum.event!.module;
expect(suricataColumnRenderer.isInstance('event.id', mockDatum)).toBe(false);
});
test('should return isInstance of false if event module does not equal suricata', () => {
mockDatum.event!.module = 'some other value';
expect(suricataColumnRenderer.isInstance('event.id', mockDatum)).toBe(false);
});
test('should return isInstance true if event is NOT empty and module equals suricata', () => {
expect(suricataColumnRenderer.isInstance('event.id', mockDatum)).toBe(true);
});
test('should return isInstance true if event is NOT empty and module equals SurICaTA', () => {
mockDatum.event!.module = 'SurICaTA';
expect(suricataColumnRenderer.isInstance('event.id', mockDatum)).toBe(true);
});
test('should return a value of the CVE if event has a valid suricata value and it is a CVE', () => {
const column = suricataColumnRenderer.renderColumn(
'event.id',
mockDatum,
allFieldsInSchemaByName['event.id']
);
const wrapper = mount(<span>{column}</span>);
expect(wrapper.text()).toEqual('CVE-2016-10174');
});
test('should return a value of the event id if no CVE is in the event', () => {
const dataumWithValue = set(
'suricata.eve.alert.signature',
'Something without a CVE entry inside of it',
mockDatum
);
const column = suricataColumnRenderer.renderColumn(
'event.id',
dataumWithValue,
allFieldsInSchemaByName['event.id']
);
const wrapper = mount(<span>{column}</span>);
expect(wrapper.text()).toEqual('4');
});
test('should return a value of the empty if no CVE is in the event and the event does not have an id', () => {
delete mockDatum.suricata!.eve!.alert!.signature;
delete mockDatum.event!.id;
const column = suricataColumnRenderer.renderColumn(
'event.id',
mockDatum,
allFieldsInSchemaByName['event.id']
);
const wrapper = mount(<span>{column}</span>);
expect(wrapper.text()).toEqual(getEmptyValue());
});
test('should return a value if an unknown column name is sent in', () => {
const column = suricataColumnRenderer.renderColumn(
'made up column name',
mockDatum,
allFieldsInSchemaByName['made up column name']
);
const wrapper = mount(<span>{column}</span>);
expect(wrapper.text()).toEqual(getEmptyValue());
});
});

View file

@ -1,45 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { get } from 'lodash/fp';
import React from 'react';
import { ColumnRenderer, getSuricataCVEFromSignature } from '.';
import { Ecs } from '../../../../graphql/types';
import { getEmptyTagValue, getOrEmptyTag } from '../../../empty_value';
const suricataColumnsOverridden = ['event.id'];
export const suricataColumnRenderer: ColumnRenderer = {
isInstance: (columnName: string, ecs: Ecs) => {
if (
suricataColumnsOverridden.includes(columnName) &&
ecs &&
ecs.event &&
ecs.event.module &&
ecs.event.module.toLowerCase() === 'suricata'
) {
return true;
}
return false;
},
renderColumn: (columnName: string, data: Ecs) => {
switch (columnName) {
case 'event.id':
const signature: string = get('suricata.eve.alert.signature', data);
const cve = getSuricataCVEFromSignature(signature);
if (cve != null) {
return <>{cve}</>;
} else {
return getOrEmptyTag('event.id', data);
}
default:
// unknown column name
return getEmptyTagValue();
}
},
};

View file

@ -0,0 +1,72 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
import toJson from 'enzyme-to-json';
import { noop } from 'lodash/fp';
import * as React from 'react';
import { DragDropContext } from 'react-beautiful-dnd';
import { Provider } from 'react-redux';
import { ThemeProvider } from 'styled-components';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { mockEcsData } from '../../../../mock';
import { mockGlobalState } from '../../../../mock';
import { createStore, State } from '../../../../store';
import { SuricataDetails } from './suricata_details';
describe('SuricataDetails', () => {
const state: State = mockGlobalState;
let store = createStore(state);
const theme = () => ({ eui: euiDarkVars, darkMode: true });
beforeEach(() => {
store = createStore(state);
});
describe('rendering', () => {
test('it renders the default SuricataDetails', () => {
const wrapper = mountWithIntl(
<ThemeProvider theme={theme}>
<Provider store={store}>
<DragDropContext onDragEnd={noop}>
<SuricataDetails data={mockEcsData[2]} />
</DragDropContext>
</Provider>
</ThemeProvider>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
test('it returns text if the data does contain suricata data', () => {
const wrapper = mountWithIntl(
<ThemeProvider theme={theme}>
<Provider store={store}>
<DragDropContext onDragEnd={noop}>
<SuricataDetails data={mockEcsData[2]} />
</DragDropContext>
</Provider>
</ThemeProvider>
);
expect(wrapper.text()).toEqual(
'4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343'
);
});
test('it returns null for text if the data contains no suricata data', () => {
const wrapper = mountWithIntl(
<ThemeProvider theme={theme}>
<Provider store={store}>
<DragDropContext onDragEnd={noop}>
<SuricataDetails data={mockEcsData[0]} />
</DragDropContext>
</Provider>
</ThemeProvider>
);
expect(wrapper.text()).toEqual(null);
});
});
});

View file

@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { get } from 'lodash/fp';
import * as React from 'react';
import { pure } from 'recompose';
import { EuiSpacer } from '@elastic/eui';
import styled from 'styled-components';
import { Ecs } from '../../../../graphql/types';
import { SourceDest } from './source_dest_ip';
import { SuricataRefs } from './suricata_refs';
import { SuricataSignature } from './suricata_signature';
const Details = styled.div`
margin-top: 10px;
margin-bottom: 10px;
`;
export const SuricataDetails = pure(({ data }: { data: Ecs }) => {
const signature: string | null = get('suricata.eve.alert.signature', data);
const signatureId: string | null = get('suricata.eve.alert.signature_id', data);
if (signatureId != null && signature != null) {
return (
<Details>
<SuricataSignature id={data._id} signature={signature} signatureId={signatureId} />
<SuricataRefs signatureId={signatureId} />
<EuiSpacer size="s" />
<SourceDest data={data} />
</Details>
);
} else {
return null;
}
});

View file

@ -0,0 +1,84 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { getBeginningTokens, getLinksFromSignature } from './suricata_links';
describe('SuricataLinks', () => {
describe('#getLinksFromSignature', () => {
test('it should return an empty array when the link does not exist', () => {
const links = getLinksFromSignature('id-madeup-does-not-exist');
expect(links).toEqual([]);
});
test('it should return a valid unique set of rules (if analyst has added duplicate refs)', () => {
const links = getLinksFromSignature('2019415');
expect(links).toEqual([
'http://cve.mitre.org/cgi-bin/cvename.cgi?name=2014-3566',
'http://blog.fox-it.com/2014/10/15/poodle/',
'http://www.openssl.org/~bodo/ssl-poodle.pdf',
'http://askubuntu.com/questions/537196/how-do-i-patch-workaround-sslv3-poodle-vulnerability-cve-2014-3566',
'http://www.imperialviolet.org/2014/10/14/poodle.html',
]);
});
});
describe('#getBeginningTokens', () => {
test('it should return valid tags of ET and PRO', () => {
const tokens = getBeginningTokens('ET PRO Some Signature');
expect(tokens).toEqual(['ET', 'PRO']);
});
test('it should return valid tags of ET SCAN SHAZAM', () => {
const tokens = getBeginningTokens('ET SCAN SHAZAM Some Signature');
expect(tokens).toEqual(['ET', 'SCAN', 'SHAZAM']);
});
test('it should return valid tag of GPL', () => {
const tokens = getBeginningTokens('GPL YeT ANoTHER Signature');
expect(tokens).toEqual(['GPL']);
});
test('it should return valid tags with special characters', () => {
const tokens = getBeginningTokens('ET IPv4 IPv6 SCAN Rebecca');
expect(tokens).toEqual(['ET', 'IPv4', 'IPv6', 'SCAN']);
});
test('it should NOT return multiple mixed tokens, but only the ones at the beginning', () => {
const tokens = getBeginningTokens('EVAN BRADEN Hassanabad FRANK');
expect(tokens).toEqual(['EVAN', 'BRADEN']);
});
test('it should return empty tags if there are no tags', () => {
const tokens = getBeginningTokens('No Tags Here');
expect(tokens).toEqual([]);
});
test('it should return empty tags if any empty string is sent in', () => {
const tokens = getBeginningTokens('');
expect(tokens).toEqual([]);
});
test('it should return empty tags if a string of all spaces is sent in', () => {
const tokens = getBeginningTokens(' ');
expect(tokens).toEqual([]);
});
test('it should return empty tags if a signature has extra spaces at the start', () => {
const tokens = getBeginningTokens(' Hello How are You?');
expect(tokens).toEqual([]);
});
test('it should return empty tags if a signature has extra spaces at the end', () => {
const tokens = getBeginningTokens('Hello How are You? ');
expect(tokens).toEqual([]);
});
test('it should return valid tags if a signature has extra spaces at the start', () => {
const tokens = getBeginningTokens(' HELLO HOW are You?');
expect(tokens).toEqual(['HELLO', 'HOW']);
});
});
});

View file

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { uniq } from 'lodash/fp';
import { db } from 'suricata-sid-db';
export const getLinksFromSignature = (id: string): string[] => {
const refs = db[id];
if (refs != null) {
return uniq(refs);
} else {
return [];
}
};
const specialTokenRules = ['IPv4', 'IPv6'];
export const getBeginningTokens = (signature: string): string[] => {
const signatureSplit = signature.trim().split(' ');
return signatureSplit.reduce<string[]>((accum, curr, index) => {
if (
(accum.length === index && curr === curr.toUpperCase() && curr !== '') ||
specialTokenRules.includes(curr)
) {
accum = accum.concat(curr);
}
return accum;
}, []);
};

View file

@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink } from '@elastic/eui';
import * as React from 'react';
import { pure } from 'recompose';
import styled from 'styled-components';
import { getLinksFromSignature } from './suricata_links';
const Icon = styled(EuiIcon)`
margin-left: 10px;
margin-right: 3px;
`;
const LinkEuiFlexItem = styled(EuiFlexItem)`
display: inline;
`;
export const SuricataRefs = pure(({ signatureId }: { signatureId: string }) => {
const links = getLinksFromSignature(signatureId);
return (
<EuiFlexGroup gutterSize="none" justifyContent="center" wrap>
{links.map(link => (
<LinkEuiFlexItem key={link} grow={false}>
<Icon type="link" size="s" />
<EuiLink href={link} color="subdued" target="_blank">
{link}
</EuiLink>
</LinkEuiFlexItem>
))}
</EuiFlexGroup>
);
});

View file

@ -62,7 +62,7 @@ describe('suricata_row_renderer', () => {
</ThemeProvider>
);
expect(wrapper.text()).toContain(
'some children ET EXPLOIT NETGEAR WNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)'
'some children 4ETEXPLOITNETGEARWNR2000v5 hidden_lang_avi Stack Overflow (CVE-2016-10174)Source192.168.0.3:53Destination192.168.0.3:6343'
);
});

View file

@ -4,49 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiButton } from '@elastic/eui';
import { get, getOr } from 'lodash/fp';
import { get } from 'lodash/fp';
import React from 'react';
import { pure } from 'recompose';
import styled, { keyframes } from 'styled-components';
import styled from 'styled-components';
import { createLinkWithSignature, RowRenderer } from '.';
import { RowRenderer } from '.';
import { Ecs } from '../../../../graphql/types';
import {
getAllFieldsInSchemaByMappedName,
getMappedEcsValue,
mappedEcsSchemaFieldNames,
virtualEcsSchema,
} from '../../../../lib/ecs';
import { escapeQueryValue } from '../../../../lib/keury';
import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper';
import { escapeDataProviderId } from '../../../drag_and_drop/helpers';
import { Provider } from '../../data_providers/provider';
import { FormattedField } from './formatted_field';
import * as i18n from './translations';
export const dropInEffect = keyframes`
0% {
border: 1px solid;
border-color: #d9d9d9;
transform: scale(1.050);
box-shadow: 0 2px 2px -1px rgba(153, 153, 153, 0.3), 0 1px 5px -2px rgba(153, 153, 153, 0.3);
}
35%, 80% {
border: 1px solid;
border-color: #d9d9d9;
transform: scale(1.010);
box-shadow: 0 2px 2px -1px rgba(153, 153, 153, 0.3), 0 1px 5px -2px rgba(153, 153, 153, 0.3);
}
100% {
border-color: transparent;
border-left: 2px solid #8ecce3;
transform: scale(1);
box-shadow: unset;
}
`;
import { SuricataDetails } from './suricata_details';
const SuricataRow = styled.div`
width: 100%;
@ -56,138 +20,16 @@ const SuricataRow = styled.div`
}
`;
const SuricataSignature = styled.div`
align-items: center;
display: flex;
flex-direction: column;
margin-top: 5px;
`;
const Details = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
margin: 5px;
min-width: 340px;
`;
const LabelValuePairContainer = styled.div`
align-items: center;
display: flex;
flex-direction: column;
`;
const Label = styled.div`
font-weight: bold;
`;
const allFieldsInSchemaByName = getAllFieldsInSchemaByMappedName(virtualEcsSchema);
export const fieldExists = ({ data, fieldName }: { data: Ecs; fieldName: string }): boolean =>
getMappedEcsValue({ data, fieldName }) != null;
const DraggableValue = pure<{ data: Ecs; fieldName: string }>(({ data, fieldName }) => {
const itemDataProvider = {
enabled: true,
id: escapeDataProviderId(`id-suricata-row-render-value-for-${fieldName}-${data._id}`),
name: `${fieldName}: ${getMappedEcsValue({
data,
fieldName,
})}`,
queryMatch: {
field: getOr(fieldName, fieldName, mappedEcsSchemaFieldNames),
value: escapeQueryValue(
getMappedEcsValue({
data,
fieldName,
})
),
},
excluded: false,
kqlQuery: '',
and: [],
};
const field = allFieldsInSchemaByName[fieldName];
const fieldType = field != null ? field.type : '';
return fieldExists({ data, fieldName }) ? (
<DraggableWrapper
key={`suricata-row-render-value-for-${fieldName}-${data._id}`}
dataProvider={itemDataProvider}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (
<DragEffects>
<Provider dataProvider={dataProvider} />
</DragEffects>
) : (
<FormattedField data={data} fieldName={fieldName} fieldType={fieldType} />
)
}
/>
) : null;
});
export const ValuesContainer = styled.div`
display: flex;
`;
export const suricataRowRenderer: RowRenderer = {
isInstance: (ecs: Ecs) => {
if (ecs && ecs.event && ecs.event.module && ecs.event.module.toLowerCase() === 'suricata') {
return true;
}
return false;
const module: string | null = get('event.module', ecs);
return module != null && module.toLowerCase() === 'suricata';
},
renderRow: (data: Ecs, children: React.ReactNode) => {
const signature = get('suricata.eve.alert.signature', data) as string;
return (
<SuricataRow>
{children}
{signature != null ? (
<SuricataSignature>
<EuiButton
key={data._id}
fill
size="s"
href={createLinkWithSignature(signature)}
target="_blank"
>
{signature}
</EuiButton>
<Details>
{fieldExists({ data, fieldName: 'source.ip' }) ? (
<LabelValuePairContainer>
<Label>{i18n.SOURCE}</Label>
<ValuesContainer>
<DraggableValue
data={data}
data-test-subj="source-ip-and-port"
fieldName={'source.ip'}
/>
{fieldExists({ data, fieldName: 'source.port' }) ? ':' : null}
<DraggableValue data={data} fieldName={'source.port'} />
</ValuesContainer>
</LabelValuePairContainer>
) : null}
{fieldExists({ data, fieldName: 'destination.ip' }) ? (
<LabelValuePairContainer>
<Label>{i18n.DESTINATION}</Label>
<ValuesContainer>
<DraggableValue
data={data}
data-test-subj="destination-ip-and-port"
fieldName={'destination.ip'}
/>
{fieldExists({ data, fieldName: 'destination.port' }) ? ':' : null}
<DraggableValue data={data} fieldName={'destination.port'} />
</ValuesContainer>
</LabelValuePairContainer>
) : null}
</Details>
</SuricataSignature>
) : null}
<SuricataDetails data={data} />
</SuricataRow>
);
},

View file

@ -0,0 +1,117 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
import toJson from 'enzyme-to-json';
import { noop } from 'lodash/fp';
import * as React from 'react';
import { Provider } from 'react-redux';
import { ThemeProvider } from 'styled-components';
import { DragDropContext } from 'react-beautiful-dnd';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { mockGlobalState } from '../../../../mock';
import { createStore, State } from '../../../../store';
import { DraggableSignatureId, GoogleLink, SuricataSignature, Tokens } from './suricata_signature';
describe('SuricataSignature', () => {
const state: State = mockGlobalState;
const theme = () => ({ eui: euiDarkVars, darkMode: true });
let store = createStore(state);
beforeEach(() => {
store = createStore(state);
});
describe('rendering', () => {
test('it renders the default SuricataSignature', () => {
const wrapper = mountWithIntl(
<ThemeProvider theme={theme}>
<Provider store={store}>
<DragDropContext onDragEnd={noop}>
<SuricataSignature
id="doc-id-123"
signatureId="id-123"
signature="ET SCAN ATTACK Hello"
/>
</DragDropContext>
</Provider>
</ThemeProvider>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
});
describe('GoogleLink', () => {
test('it renders text passed in as value', () => {
const wrapper = mountWithIntl(
<GoogleLink link={'http:/example.com/'} value={'Example Link'} />
);
expect(wrapper.text()).toEqual('Example Link');
});
test('it renders props passed in as link', () => {
const wrapper = mountWithIntl(
<GoogleLink link={'http:/example.com/'} value={'Example Link'} />
);
expect(wrapper.find('a').prop('href')).toEqual(
'https://www.google.com/search?q=http:/example.com/'
);
});
test("it encodes <script>alert('XSS')</script>", () => {
const wrapper = mountWithIntl(
<GoogleLink
link={"http:/example.com?q=<script>alert('XSS')</script>"}
value={'Example Link'}
/>
);
expect(wrapper.find('a').prop('href')).toEqual(
"https://www.google.com/search?q=http:/example.com?q=%3Cscript%3Ealert('XSS')%3C/script%3E"
);
});
});
describe('Tokens', () => {
test('should render empty if tokens are empty', () => {
const wrapper = mountWithIntl(<Tokens tokens={[]} />);
expect(wrapper.text()).toEqual(null);
});
test('should render a single if it is present', () => {
const wrapper = mountWithIntl(
<div>
<Tokens tokens={['ET']} />
</div>
);
expect(wrapper.text()).toEqual('ET');
});
test('should render the multiple tokens if they are present', () => {
const wrapper = mountWithIntl(
<div>
<Tokens tokens={['ET', 'SCAN']} />
</div>
);
expect(wrapper.text()).toEqual('ETSCAN');
});
});
describe('DraggableSignatureId', () => {
test('it renders the default SuricataSignature', () => {
const wrapper = mountWithIntl(
<ThemeProvider theme={theme}>
<Provider store={store}>
<DragDropContext onDragEnd={noop}>
<DraggableSignatureId id="id-123" signatureId="signature-123" />
</DragDropContext>
</Provider>
</ThemeProvider>
);
expect(wrapper.text()).toEqual('signature-123');
});
});
});

View file

@ -0,0 +1,102 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
import * as React from 'react';
import { pure } from 'recompose';
import styled from 'styled-components';
import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper';
import { escapeDataProviderId } from '../../../drag_and_drop/helpers';
import { Provider } from '../../../timeline/data_providers/provider';
import { getBeginningTokens } from './suricata_links';
const SignatureFlexItem = styled(EuiFlexItem)`
min-width: 77px;
`;
const Badge = styled(EuiBadge)`
vertical-align: top;
`;
const TokensFlexItem = styled(EuiFlexItem)`
margin-left: 3px;
`;
const LinkFlexItem = styled(EuiFlexItem)`
margin-left: 6px;
`;
export const GoogleLink = pure(({ link, value }: { link: string; value: string }) => (
<LinkFlexItem grow={false}>
<EuiLink href={`https://www.google.com/search?q=${encodeURI(link)}`} target="_blank">
{value}
</EuiLink>
</LinkFlexItem>
));
export const Tokens = pure(({ tokens }: { tokens: string[] }) => (
<>
{tokens.map(token => (
<TokensFlexItem key={token} grow={false}>
<EuiBadge iconType="tag" color="hollow">
{token}
</EuiBadge>
</TokensFlexItem>
))}
</>
));
export const DraggableSignatureId = pure(
({ id, signatureId }: { id: string; signatureId: string }) => (
<SignatureFlexItem grow={false}>
<DraggableWrapper
dataProvider={{
and: [],
enabled: true,
id: escapeDataProviderId(`suricata-${id}-sig-${signatureId}`),
name: signatureId,
excluded: false,
kqlQuery: '',
queryMatch: {
field: 'suricata.eve.alert.signature_id',
value: signatureId,
},
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (
<DragEffects>
<Provider dataProvider={dataProvider} />
</DragEffects>
) : (
<Badge iconType="number" color="hollow">
{signatureId}
</Badge>
)
}
/>
</SignatureFlexItem>
)
);
export const SuricataSignature = pure(
({ id, signature, signatureId }: { id: string; signature: string; signatureId: string }) => {
const tokens = getBeginningTokens(signature);
return (
<EuiFlexGroup justifyContent="center" gutterSize="none">
<DraggableSignatureId id={id} signatureId={signatureId} />
<Tokens tokens={tokens} />
<GoogleLink
link={signature}
value={signature
.split(' ')
.splice(tokens.length)
.join(' ')}
/>
</EuiFlexGroup>
);
}
);

View file

@ -90,3 +90,6 @@ export const getPopulatedMappedFields = ({
'name',
getAllFieldsInSchema(schema).filter(f => getMappedEcsValue({ data, fieldName: f.name }) != null)
);
export const fieldExists = ({ data, fieldName }: { data: Ecs; fieldName: string }): boolean =>
getMappedEcsValue({ data, fieldName }) != null;

View file

@ -85,8 +85,7 @@ export const eventBaseFieldsMap: Readonly<Record<string, string>> = {
// NOTE: This is only for the index filebeat. If you're using auditbeat, then this needs to be changed out for 'event.id': 'event.id'
'event.id': 'suricata.eve.flow_id',
'event.module': 'event.module',
// NOTE: This is only for the index filebeat. If you're using auditbeat, this doesn't matter as auditbeat does not have severities yet.
'event.severity': 'suricata.eve.alert.severity',
'event.severity': 'event.severity',
'event.type': 'event.type',
};

View file

@ -57,6 +57,7 @@ describe('events elasticsearch_adapter', () => {
module: 'event-module-1',
type: 'event-type-1',
category: 'event-category-1',
severity: 1,
},
},
sort: ['123567890', '1234'],

View file

@ -21178,6 +21178,13 @@ supports-color@^6.1.0:
dependencies:
has-flag "^3.0.0"
suricata-sid-db@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/suricata-sid-db/-/suricata-sid-db-1.0.2.tgz#96ceda4db117a9f1282c8f9d785285e5ccf342b1"
integrity sha512-13h9BiGWLQ2Ng5yrtVkwYudjo1yFalRIW63qsw7/awb8sJZjptmVSIg/iyfFAB/TV1Ru8Kgt9Q95CZwgUipF1w==
dependencies:
typescript "^3.3.3333"
svg-tags@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
@ -22534,6 +22541,11 @@ typescript@^3.0.3:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.0.3.tgz#4853b3e275ecdaa27f78fda46dc273a7eb7fc1c8"
integrity sha512-kk80vLW9iGtjMnIv11qyxLqZm20UklzuR2tL0QAnDIygIUIemcZMxlMWudl9OOt76H3ntVzcTiddQ1/pAAJMYg==
typescript@^3.3.3333:
version "3.3.3333"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.3333.tgz#171b2c5af66c59e9431199117a3bcadc66fdcfd6"
integrity sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==
ua-parser-js@^0.7.18, ua-parser-js@^0.7.9:
version "0.7.18"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed"