Skip to content

Commit

Permalink
first pass at arkime cont3xt card (arkime#2494)
Browse files Browse the repository at this point in the history
* first pass at arkime cont3xt card

* added precont3xt:dev

* add protocol aggregations to arkime card

and document custom buttons in pivot dropdown

---------

Co-authored-by: Andy Wick <[email protected]>
  • Loading branch information
31453 and awick authored Nov 2, 2023
1 parent 0085c9d commit 87b4b19
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 16 deletions.
8 changes: 7 additions & 1 deletion cont3xt/descriptions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ Card Description - an object of the following
* json - just display raw json, call in JSON.stringify(blah, false, 2)
* dnsRecords - display of non-A/AAAA dns records (for use with DNS integration and `path: []`)
* defang - when true defang the string, change http to hXXp and change . to [.]
* pivot - when set this field should be added to action menu for table entry that you can replace query with
* pivot - when set this field should include a dropdown action menu, options include:
* copy - show a copy option in the pivot dropdown to copy the value
* pivot - replace query with the value
* custom - create a custom button
* field - requires path (see path above)
* name - name to display on the button
* href - the url for the button. use %{var} to replace pieces of the url when constructing the card
* join - with array, display with value as the separator on one line (example single: ', ')
* fieldRoot - for arrays/tables, the path (it can have dots) from each object in the array to its desired field. Effectively maps the root array of objects to an array of values/sub-objects.
* fieldRootPath - alternative to fieldRoot, this is the fieldRoot path pre-separated into a string array (eg. fieldRoot: 'foo.bar' is equivalent to fieldRootPath: ['foo', 'bar'])
Expand Down
113 changes: 104 additions & 9 deletions cont3xt/integrations/arkime/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,88 @@ class ArkimeIntegration extends Integration {
};

card = {
fields: [
]
fields: [{
label: 'Protocols',
field: 'protocols',
type: 'table',
fields: [
{
label: 'Protocol',
field: 'key'
},
{
label: 'Count',
field: 'doc_count'
}
]
}, {
label: 'Sessions',
field: 'hits',
type: 'table',
fields: [
{
label: 'Source IP',
field: 'source.ip',
pivot: true,
options: {
srcip: {
field: {
path: ['source', 'ip']
},
name: 'Arkime Src IP Query',
href: '%{arkimeUrl}/sessions?expression=ip.src==%{value}'
},
id: {
field: {
path: ['id']
},
name: 'Arkime Session',
href: '%{arkimeUrl}/sessions?expression=id==%{value}'
},
copy: 'Copy',
pivot: 'Pivot'
}
},
{
label: 'Source Port',
field: 'source.port'
},
{
label: 'Destination IP',
field: 'destination.ip',
pivot: true,
options: {
dstip: {
field: {
path: ['source', 'ip']
},
name: 'Arkime Dst IP Query',
href: '%{arkimeUrl}/sessions?expression=ip.dst==%{value}'
},
id: {
field: {
path: ['id']
},
name: 'Arkime Session',
href: '%{arkimeUrl}/sessions?expression=id==%{value}'
},
copy: 'Copy',
pivot: 'Pivot'
}
},
{
label: 'Destination Port',
field: 'destination.port'
}
]
}]
};

// ----------------------------------------------------------------------------

#prefix;
#client;
#arkimeUrl;
#searchDays;
#maxResults;

Expand All @@ -48,6 +122,26 @@ class ArkimeIntegration extends Integration {
this.#prefix = ArkimeUtil.formatPrefix(ArkimeConfig.getFull(section, 'prefix'));
this.#searchDays = parseInt(ArkimeConfig.getFull(section, 'searchDays', -1), 10);
this.#maxResults = ArkimeConfig.getFull(section, 'maxResults', 20);
this.#arkimeUrl = ArkimeConfig.getFull(section, 'arkimeUrl', 'http://localhost:8123');
if (this.#arkimeUrl.endsWith('/')) {
this.#arkimeUrl = this.#arkimeUrl.slice(0, -1);
}

const time = this.#searchDays === -1 ? 'ALL' : `${this.#searchDays} days`;
this.card.title = `${this.name} | Searching ${time} | Displaying ${this.#maxResults} results`;
// replace href with correct arkime url and add date to query
this.card.fields.forEach((field) => {
if (!field.fields) { return; }
field.fields.forEach((subfield) => {
if (!subfield.options) { return; }
for (const option in subfield.options) {
if (subfield.options[option].href) {
subfield.options[option].href = subfield.options[option].href.replace('%{arkimeUrl}', this.#arkimeUrl);
subfield.options[option].href += `&date=${this.#searchDays === -1 ? -1 : this.#searchDays * 24}`;
}
}
});
});

const elasticsearch = ArkimeConfig.getFullArray(section, 'elasticsearch', 'http://localhost:9200');
const elasticsearchAPIKey = ArkimeConfig.getFull(section, 'elasticsearchAPIKey');
Expand Down Expand Up @@ -98,11 +192,14 @@ class ArkimeIntegration extends Integration {
]
}
},
aggregations: {
protocols: { terms: { field: 'protocol' } }
},
sort: { lastPacket: { order: 'desc' } },
size: this.#maxResults
};

if (this.#searchDays !== -1) {
if (this.#searchDays === -1) {
query.query.bool.must.shift();
}

Expand All @@ -116,15 +213,13 @@ class ArkimeIntegration extends Integration {
return Integration.NoResult;
}

const data = {
hits: results.body.hits.hits.map(i => {
return i._source;
}),
return {
protocols: results.body.aggregations.protocols.buckets,
hits: results.body.hits.hits.map(i => ({ ...i._source, id: i._id })),
_cont3xt: {
count: results.body.hits.hits.length
count: results.body.hits.total
}
};
return data;
}

// ----------------------------------------------------------------------------
Expand Down
16 changes: 10 additions & 6 deletions cont3xt/vueapp/src/components/integrations/IntegrationValue.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ SPDX-License-Identifier: Apache-2.0
<span class="text-warning">
{{ field.label }}
<span
class="fa"
v-if="field.type === 'table' || field.type === 'array'"
:class="{'fa-caret-down':visible,'fa-caret-up':!visible}"
class="fa"
v-if="field.type === 'table' || field.type === 'array'"
:class="{'fa-caret-down':visible,'fa-caret-up':!visible}"
/>
</span>
<span v-if="field.type === 'table'"
:class="getTableLength() === 0 ? 'table-count-low' : 'text-default'">({{ getTableLength() }})
:class="getTableLength() === 0 ? 'table-count-low' : 'text-default'">({{ getTableLength() }})
</span>
</label>
<div class="d-inline">
Expand Down Expand Up @@ -137,14 +137,18 @@ SPDX-License-Identifier: Apache-2.0
<template v-else>
<template v-if="field.pivot">
<cont3xt-field
pull-left
:data="data"
:value="value.value"
:options="field.options"
:highlights="highlights"
/>
</template>
<template v-else>
<highlightable-text
:content="value.value"
:highlights="highlights"/>
:content="value.value"
:highlights="highlights"
/>
</template>
</template> <!-- /default string, ms, seconds, & date field -->
</template>
Expand Down
21 changes: 21 additions & 0 deletions cont3xt/vueapp/src/utils/Field.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,23 @@ SPDX-License-Identifier: Apache-2.0
class="field-dropdown"
data-testid="field-dropdown"
:class="{'pull-right':!pullLeft,'pull-left':pullLeft}">
<template v-for="option in options">
<b-dropdown-item
target="_blank"
:key="option.name"
v-if="option.href"
:href="formatUrl(option)">
{{ option.name }}
</b-dropdown-item>
</template>
<b-dropdown-item
key="copy"
v-if="options.copy"
@click="doCopy(value)">
{{ options.copy }}
</b-dropdown-item>
<b-dropdown-item
key="pivot"
target="_blank"
v-if="options.pivot"
:href="`?b=${base64Encode(value)}`">
Expand All @@ -35,13 +46,19 @@ SPDX-License-Identifier: Apache-2.0

<script>
import HighlightableText from '@/utils/HighlightableText';
import { formatPostProcessedValue } from '@/utils/formatValue';

export default {
name: 'Cont3xtField',
components: {
HighlightableText
},
props: {
data: { // the parent data row for replacing values in urls
// only necessary for pivot fields that have a url
type: Object,
default: () => { return {}; }
},
value: { // the value to be used in copy and display if no display value
type: String,
required: true
Expand Down Expand Up @@ -70,6 +87,10 @@ export default {
};
},
methods: {
formatUrl (option) {
const value = formatPostProcessedValue(this.data, option.field);
return option.href.replace('%{value}', value);
},
/** Toggles the dropdown menu options for a field */
toggleDropdown () {
this.isOpen = !this.isOpen;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"viewer:bundle": "webpack --progress --config viewer/vueapp/build/webpack.dev.conf.js",
"viewer:start": "cd viewer && node viewer.js -c ../config.ini",
"previewer:start": "npm run viewer:build",
"precont3xt:dev": "(cd viewer && node addUser.js -c ../tests/config.test.ini -n testuser admin admin admin --admin --packetSearch --createOnly)",
"previewer:dev": "(cd viewer && node addUser.js -c ../tests/config.test.ini -n testuser admin admin admin --admin --packetSearch --createOnly)",
"viewer:dev": "(cd viewer && NODE_ENV=development nodemon viewer.js -c ../tests/config.test.ini -n testuser) & webpack --watch --progress --config viewer/vueapp/build/webpack.dev.conf.js",
"viewer:test": "(cd viewer && NODE_ENV=development nodemon viewer.js --debug -c ../tests/config.test.ini -n test) & webpack --watch --progress --config viewer/vueapp/build/webpack.dev.conf.js",
Expand Down

0 comments on commit 87b4b19

Please sign in to comment.