Skip to content

Commit

Permalink
Merge pull request #7 from polarityio/develop
Browse files Browse the repository at this point in the history
feat:  add search observables
  • Loading branch information
ntsmith7 authored Jul 19, 2023
2 parents 7eea12f + b812426 commit cc9e85f
Show file tree
Hide file tree
Showing 11 changed files with 1,384 additions and 76 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,23 @@ Please see [OpenCTI](https://www.opencti.io/) for more information.
## OpenCTI Integration Options

### OpenCTI URL

The Base URL for your OpenCTI instance including the scheme. (i.e. - https://myopenctiserver). Option must be set to "Users can view only".

### OpenCTI API Key

Valid OpenCTI API Key found in your OpenCTI user account profile.

## Open Data Sources

The OpenCTI integration will query for Observables or Indicators. Select the data sources you would like to query for.

## Installation Instructions

Installation instructions for integrations are provided on the [PolarityIO GitHub Page](https://polarityio.github.io/).

## Polarity
Polarity is a memory-augmentation platform that improves and accelerates analyst decision making. For more information about the Polarity platform please see:

Polarity is a memory-augmentation platform that improves and accelerates analyst decision making. For more information about the Polarity platform please see:

https://polarity.io/
30 changes: 22 additions & 8 deletions components/block.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
polarity.export = PolarityComponent.extend({
details: Ember.computed.alias('block.data.details'),
webUrl: Ember.computed('block.userOptions.url', function(){
const url = this.get('block.userOptions.url');
return url.endsWith('/') ? url : `${url}/`;
}),
timezone: Ember.computed("Intl", function () {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
})
details: Ember.computed.alias('block.data.details'),
webUrl: Ember.computed('block.userOptions.url', function () {
const url = this.get('block.userOptions.url');
return url.endsWith('/') ? url : `${url}/`;
}),
timezone: Ember.computed('Intl', function () {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
}),
expandableTitleStates: {},
actions: {
toggleExpandableTitle: function (index) {
const modifiedExpandableTitleStates = Object.assign(
{},
this.get('expandableTitleStates'),
{
[index]: !this.get('expandableTitleStates')[index]
}
);

this.set(`expandableTitleStates`, modifiedExpandableTitleStates);
}
}
});
31 changes: 28 additions & 3 deletions config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module.exports = {
*/
description:
'OpenCTI is an open source platform allowing organizations to store, organize, visualize and share their knowledge on cyber threats.',
entityTypes: ['ipv4', 'domain', 'hash', 'email'],
entityTypes: ['IPv4', 'domain', 'hash', 'email'],
styles: ['./styles/style.less'],
defaultColor: 'light-gray',
/**
Expand Down Expand Up @@ -54,7 +54,7 @@ module.exports = {
ca: '',
// An HTTP proxy to be used. Supports proxy Auth with Basic Auth, identical to support for
// the url parameter (by embedding the auth info in the uri)
proxy: ""
proxy: ''
},
logging: {
level: 'info' //trace, debug, info, warn, error, fatal
Expand All @@ -70,7 +70,8 @@ module.exports = {
{
key: 'url',
name: 'OpenCTI URL',
description: 'The Base URL for your OpenCTI instance including the scheme. (i.e. - https://myopenctiserver). Option must be set to "Users can view only"',
description:
'The Base URL for your OpenCTI instance including the scheme. (i.e. - https://myopenctiserver). Option must be set to "Users can view only"',
type: 'text',
default: '',
userCanEdit: false,
Expand All @@ -84,6 +85,30 @@ module.exports = {
type: 'password',
userCanEdit: false,
adminOnly: true
},
{
key: 'dataSources',
name: 'Data Sources',
description:
'Select the data sources you would like to use for the OpenCTI integration. If no data sources are selected, all data sources will be used.',
default: {
value: 'observable',
display: 'Observable'
},
type: 'select',
options: [
{
value: 'observable',
display: 'Observable'
},
{
value: 'indicators',
display: 'Indicators'
}
],
multiple: false,
userCanEdit: false,
adminOnly: true
}
]
};
36 changes: 26 additions & 10 deletions config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,9 @@
"name": "OpenCTI",
"acronym": "OCTI",
"description": "OpenCTI is an open source platform allowing organizations to store, organize, visualize and share their knowledge on cyber threats.",
"entityTypes": [
"IPv4",
"domain",
"hash",
"email"
],
"entityTypes": ["domain", "hash", "email", "IPv4"],
"defaultColor": "light-gray",
"styles": [
"./styles/style.less"
],
"styles": ["./styles/style.less"],
"block": {
"component": {
"file": "./components/block.js"
Expand Down Expand Up @@ -49,6 +42,29 @@
"type": "password",
"userCanEdit": false,
"adminOnly": true
},
{
"key": "dataSources",
"name": "Data Sources",
"description": "Select the data sources you would like to use for the OpenCTI integration. If no data sources are selected, all data sources will be used.",
"default": {
"value": "observable",
"display": "Observable"
},
"type": "select",
"options": [
{
"value": "observable",
"display": "Observable"
},
{
"value": "indicators",
"display": "Indicators"
}
],
"multiple": false,
"userCanEdit": false,
"adminOnly": true
}
]
}
}
92 changes: 59 additions & 33 deletions integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@ const async = require('async');
const fs = require('fs');
const _ = require('lodash');
const fp = require('lodash/fp');
const query = require('./query');
const { setLogger } = require('./logger');
const { indicatorsQuery, observablesQuery } = require('./query');

let Logger;
let Logger = null;
let requestWithDefaults;

const MAX_PARALLEL_LOOKUPS = 10;

function startup(logger) {
let defaults = {};
const defaults = {};

Logger = logger;
setLogger(Logger);

const { cert, key, passphrase, ca, proxy, rejectUnauthorized } = config.request;

Expand Down Expand Up @@ -50,7 +53,8 @@ function doLookup(entities, options, cb) {
let lookupResults = [];
let tasks = [];

Logger.debug({ entities }, 'doLookup');
const query =
options.dataSources.value === 'observable' ? observablesQuery : indicatorsQuery;

entities.forEach((entity) => {
let requestOptions = {
Expand All @@ -60,22 +64,21 @@ function doLookup(entities, options, cb) {
Authorization: 'Bearer ' + options.apiKey
},
body: {
query: query,
query,
variables: {
search: entity.value,
first: 5,
orderBy: 'valid_until',
// orderBy: 'valid_until',
orderMode: 'desc'
}
},
json: true
};

Logger.trace({ requestOptions }, 'Request Options');

tasks.push(function (done) {
requestWithDefaults(requestOptions, function (error, res, body) {
if (error) {
Logger.trace({ error }, 'Error encountered');
return done({
detail: 'HTTP error encountered',
error
Expand All @@ -84,6 +87,8 @@ function doLookup(entities, options, cb) {

let processedResult = handleRestError(entity, res, body, options);

Logger.trace({ processedResult }, 'Processed Result');

if (processedResult.error) {
done(processedResult);
return;
Expand All @@ -96,22 +101,21 @@ function doLookup(entities, options, cb) {

async.parallelLimit(tasks, MAX_PARALLEL_LOOKUPS, (err, results) => {
if (err) {
Logger.error({ err: err }, 'Error');
cb(err);
return;
}

results.forEach((result) => {
if (
!_.get(result, 'data.body') ||
_.get(result, 'data.body.data.indicators.edges.length', []) === 0
_.get(result, 'data.body.data.indicators.edges.length', []) === 0 ||
_.get(result, 'data.body.data.stixCyberObservables.edges.length', []) === 0
) {
lookupResults.push({
entity: result.data.entity,
data: null
});
} else {
Logger.trace({ RESULT: result });
lookupResults.push({
entity: result.data.entity,
data: {
Expand All @@ -122,30 +126,48 @@ function doLookup(entities, options, cb) {
}
});

Logger.debug({ lookupResults }, 'Results');
Logger.trace({ lookupResults }, 'Lookup Results');
cb(null, lookupResults);
});
}

function getSummaryTags(body) {
const tags = [];
let maxScore = 0;
let confidence = 'NA';
const globalCount = fp.get('data.indicators.pageInfo.globalCount', body);
const edges = fp.get('data.indicators.edges', body, []);
edges.forEach((edge) => {
const score = fp.get('node.x_opencti_score', edge, 0);
if (score > maxScore) {
maxScore = score;
confidence = fp.get('node.confidence', edge, 'N/A');

['stixCyberObservables', 'indicators'].forEach((type) => {
if (_.get(body, `data.${type}.edges.length`, 0) > 0) {
let maxScore = 0;
let confidence = 'NA';
const globalCount = fp.get(`data.${type}.pageInfo.globalCount`, body);
const edges = fp.get(`data.${type}.edges`, body, []);

edges.forEach((edge) => {
const score = fp.get('node.x_opencti_score', edge, 0);
if (score > maxScore) {
maxScore = score;

if (type === 'indicators') {
confidence = fp.get('node.confidence', edge, 'N/A');
}
}
});

if (type === 'stixCyberObservables') {
tags.push(`Observable Count: ${globalCount}`);
tags.push(`Max Score: ${maxScore}`);
}

if (type === 'indicators') {
tags.push(`Count: ${globalCount}`);
tags.push(
`${
globalCount > 1 ? 'Max Score: ' : 'Score: '
} ${maxScore} / Confidence: ${confidence}`
);
}
}
});
tags.push(`Indicator Count: ${globalCount}`);
tags.push(
`${
globalCount > 1 ? 'Max Score: ' : 'Score: '
} ${maxScore} / Confidence: ${confidence}`
);

return tags;
}

Expand All @@ -162,14 +184,18 @@ function getSummaryTags(body) {
* @returns {{detail: *, errors: *, statusCode: *}|{data: {body, entity}, error: null}}
*/
const handleRestError = (entity, res, body, options) => {
Logger.trace({ res, body }, 'API Response');
const errors = _.get(res, 'body.errors', []);

if (
res.statusCode === 200 &&
errors.length === 0 &&
_.get(res, 'body.data.indicators')
) {
Logger.trace({ entity, res, body, options }, 'Processed Result');

Logger.trace({ errors }, 'Errors');

const dataFound =
_.get(res, 'body.data.indicators') || _.get(res, 'body.data.stixCyberObservables');

Logger.trace({ dataFound }, 'Data Found');

if (res.statusCode === 200 && errors.length === 0 && dataFound) {
return {
error: null,
data: {
Expand Down
17 changes: 17 additions & 0 deletions logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright (c) 2022, Polarity.io, Inc.
*/
let logger;

function setLogger (Logger) {
logger = Logger;
}

function getLogger () {
return logger;
}

module.exports = {
setLogger,
getLogger
};
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "opencti",
"version": "3.1.3",
"version": "3.2.0",
"main": "./integration.js",
"private": true,
"dependencies": {
"async": "^3.2.4",
"lodash": "^4.17.21",
"postman-request": "^2.88.1-postman.31"
}
}
}
Loading

0 comments on commit cc9e85f

Please sign in to comment.