wazuh-kibana-app/server/controllers/wazuh-elastic.js

668 lines
20 KiB
JavaScript
Raw Normal View History

/*
* Wazuh app - Class for Wazuh-Elastic functions
* Copyright (C) 2018 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
2018-09-10 08:32:49 +00:00
import { ElasticWrapper } from '../lib/elastic-wrapper';
2018-09-03 09:46:55 +00:00
import { ErrorResponse } from './error-response';
2018-09-10 08:32:49 +00:00
import { log } from '../logger';
2018-10-05 13:10:59 +00:00
import { getConfiguration } from '../lib/get-configuration';
2018-09-10 08:32:49 +00:00
import {
AgentsVisualizations,
OverviewVisualizations,
ClusterVisualizations
} from '../integration-files/visualizations';
2018-12-03 15:07:59 +00:00
import { Base } from '../reporting/base-query';
2018-09-03 09:46:55 +00:00
export class WazuhElasticCtrl {
/**
2018-12-13 10:02:53 +00:00
* Constructor
* @param {*} server
*/
2018-09-10 08:32:49 +00:00
constructor(server) {
this.wzWrapper = new ElasticWrapper(server);
}
/**
2018-12-13 10:02:53 +00:00
* This get the timestamp field
* @param {Object} req
* @param {Object} reply
* @returns {Object} timestamp field or ErrorResponse
*/
2018-09-10 08:32:49 +00:00
async getTimeStamp(req, reply) {
try {
const data = await this.wzWrapper.getWazuhVersionIndexAsSearch();
2018-12-19 11:10:14 +00:00
const source =
((((data || {}).hits || {}).hits || [])[0] || {})._source || {};
2018-12-19 11:04:57 +00:00
if (source.installationDate && source.lastRestart) {
2018-09-10 08:32:49 +00:00
return reply({
installationDate: data.hits.hits[0]._source.installationDate,
lastRestart: data.hits.hits[0]._source.lastRestart
});
} else {
throw new Error('Could not fetch .wazuh-version index');
}
} catch (error) {
return ErrorResponse(
error.message || 'Could not fetch .wazuh-version index',
4001,
500,
reply
);
}
2018-09-10 08:32:49 +00:00
}
/**
2018-12-13 10:02:53 +00:00
* This retrieve a template from Elasticsearch
* @param {Object} req
* @param {Object} reply
* @returns {Object} template or ErrorResponse
*/
2018-09-10 08:32:49 +00:00
async getTemplate(req, reply) {
try {
if (!req.params || !req.params.pattern) {
throw new Error(
'An index pattern is needed for checking the Elasticsearch template existance'
);
}
const data = await this.wzWrapper.getTemplates();
if (!data || typeof data !== 'string') {
throw new Error(
'An unknown error occurred when fetching templates from Elasticseach'
);
}
const lastChar = req.params.pattern[req.params.pattern.length - 1];
2018-12-19 11:10:14 +00:00
// Split into separate patterns
const tmpdata = data.match(/\[.*\]/g);
const tmparray = [];
2018-12-19 11:10:14 +00:00
for (let item of tmpdata) {
// A template might use more than one pattern
2018-12-19 11:10:14 +00:00
if (item.includes(',')) {
item = item.substr(1).slice(0, -1);
2018-12-19 11:10:14 +00:00
const subItems = item.split(',');
for (const subitem of subItems) {
tmparray.push(`[${subitem.trim()}]`);
}
} else {
2018-12-19 11:10:14 +00:00
tmparray.push(item);
}
}
2018-12-19 11:10:14 +00:00
// Ensure we are handling just patterns
2018-12-19 11:10:14 +00:00
const array = tmparray.filter(
item => item.includes('[') && item.includes(']')
);
2018-09-10 08:32:49 +00:00
const pattern =
lastChar === '*' ? req.params.pattern.slice(0, -1) : req.params.pattern;
2018-10-28 09:43:09 +00:00
const isIncluded = array.filter(item => {
item = item.slice(1, -1);
const lastChar = item[item.length - 1];
item = lastChar === '*' ? item.slice(0, -1) : item;
return item.includes(pattern) || pattern.includes(item);
});
2018-09-10 08:32:49 +00:00
return isIncluded && Array.isArray(isIncluded) && isIncluded.length
? reply({
2018-12-13 10:02:53 +00:00
statusCode: 200,
status: true,
data: `Template found for ${req.params.pattern}`
})
2018-09-10 08:32:49 +00:00
: reply({
2018-12-13 10:02:53 +00:00
statusCode: 200,
status: false,
data: `No template found for ${req.params.pattern}`
});
2018-09-10 08:32:49 +00:00
} catch (error) {
2018-10-01 07:56:50 +00:00
log('GET /elastic/template/{pattern}', error.message || error);
2018-09-10 08:32:49 +00:00
return ErrorResponse(
`Could not retrieve templates from Elasticsearch due to ${error.message ||
2018-12-13 10:02:53 +00:00
error}`,
2018-09-10 08:32:49 +00:00
4002,
500,
reply
);
}
2018-09-10 08:32:49 +00:00
}
/**
2018-12-13 10:02:53 +00:00
* This check index-pattern
* @param {Object} req
* @param {Object} reply
* @returns {Object} status obj or ErrorResponse
*/
2018-09-10 08:32:49 +00:00
async checkPattern(req, reply) {
try {
const response = await this.wzWrapper.getAllIndexPatterns();
const filtered = response.hits.hits.filter(
item => item._source['index-pattern'].title === req.params.pattern
);
return filtered.length >= 1
? reply({ statusCode: 200, status: true, data: 'Index pattern found' })
: reply({
2018-12-13 10:02:53 +00:00
statusCode: 500,
status: false,
error: 10020,
message: 'Index pattern not found'
});
2018-09-10 08:32:49 +00:00
} catch (error) {
2018-10-01 07:56:50 +00:00
log('GET /elastic/index-patterns/{pattern}', error.message || error);
2018-09-10 08:32:49 +00:00
return ErrorResponse(
`Something went wrong retrieving index-patterns from Elasticsearch due to ${error.message ||
2018-12-13 10:02:53 +00:00
error}`,
2018-09-10 08:32:49 +00:00
4003,
500,
reply
);
}
}
/**
* This get the fields keys
2018-12-13 10:02:53 +00:00
* @param {Object} req
* @param {Object} reply
* @returns {Array<Object>} fields or ErrorResponse
*/
2018-09-10 08:32:49 +00:00
async getFieldTop(req, reply) {
try {
// Top field payload
let payload = {
size: 1,
query: {
bool: {
must: [],
filter: { range: { '@timestamp': {} } }
}
},
aggs: {
'2': {
terms: {
field: '',
size: 1,
order: { _count: 'desc' }
2018-01-10 17:00:06 +00:00
}
2018-09-10 08:32:49 +00:00
}
2018-03-22 12:35:58 +00:00
}
2018-09-10 08:32:49 +00:00
};
// Set up time interval, default to Last 24h
const timeGTE = 'now-1d';
const timeLT = 'now';
payload.query.bool.filter.range['@timestamp']['gte'] = timeGTE;
payload.query.bool.filter.range['@timestamp']['lt'] = timeLT;
// Set up match for default cluster name
payload.query.bool.must.push(
req.params.mode === 'cluster'
? { match: { 'cluster.name': req.params.cluster } }
: { match: { 'manager.name': req.params.cluster } }
);
payload.aggs['2'].terms.field = req.params.field;
payload.pattern = req.params.pattern;
const data = await this.wzWrapper.searchWazuhAlertsWithPayload(payload);
return data.hits.total === 0 ||
typeof data.aggregations['2'].buckets[0] === 'undefined'
? reply({ statusCode: 200, data: '' })
: reply({
2018-12-13 10:02:53 +00:00
statusCode: 200,
data: data.aggregations['2'].buckets[0].key
});
2018-09-10 08:32:49 +00:00
} catch (error) {
return ErrorResponse(error.message || error, 4004, 500, reply);
}
2018-09-10 08:32:49 +00:00
}
/**
* This get the elastic setup settings
2018-12-13 10:02:53 +00:00
* @param {Object} req
* @param {Object} reply
* @returns {Object} setup info or ErrorResponse
*/
2018-09-10 08:32:49 +00:00
async getSetupInfo(req, reply) {
try {
const data = await this.wzWrapper.getWazuhVersionIndexAsSearch();
return data.hits.total === 0
? reply({ statusCode: 200, data: '' })
: reply({ statusCode: 200, data: data.hits.hits[0]._source });
} catch (error) {
2018-10-01 07:56:50 +00:00
log('GET /elastic/setup', error.message || error);
2018-09-10 08:32:49 +00:00
return ErrorResponse(
`Could not get data from elasticsearch due to ${error.message ||
2018-12-13 10:02:53 +00:00
error}`,
2018-09-10 08:32:49 +00:00
4005,
500,
reply
);
}
2018-09-10 08:32:49 +00:00
}
/**
* Checks one by one if the requesting user has enough privileges to use
* an index pattern from the list.
* @param {Array<Object>} list List of index patterns
* @param {Object} req
* @returns {Array<Object>} List of allowed index
2018-09-10 08:32:49 +00:00
*/
async filterAllowedIndexPatternList(list, req) {
let finalList = [];
for (let item of list) {
let results = false,
forbidden = false;
try {
results = await this.wzWrapper.searchWazuhElementsByIndexWithRequest(
req,
item.title
);
} catch (error) {
forbidden = true;
}
if (
2018-12-18 15:19:05 +00:00
((results || {}).hits || {}).total >= 1 ||
(!forbidden && ((results || {}).hits || {}).total === 0)
2018-09-10 08:32:49 +00:00
) {
finalList.push(item);
}
}
2018-09-10 08:32:49 +00:00
return finalList;
}
/**
* Checks for minimum index pattern fields in a list of index patterns.
* @param {Array<Object>} indexPatternList List of index patterns
*/
validateIndexPattern(indexPatternList) {
const minimum = ['@timestamp', 'full_log', 'manager.name', 'agent.id'];
let list = [];
for (const index of indexPatternList) {
let valid, parsed;
try {
parsed = JSON.parse(index._source['index-pattern'].fields);
} catch (error) {
continue;
}
valid = parsed.filter(item => minimum.includes(item.name));
if (valid.length === 4) {
list.push({
id: index._id.split('index-pattern:')[1],
title: index._source['index-pattern'].title
});
}
}
2018-09-10 08:32:49 +00:00
return list;
}
/**
* This get the list of index-patterns
2018-12-13 10:02:53 +00:00
* @param {Object} req
* @param {Object} reply
* @returns {Array<Object>} list of index-patterns or ErrorResponse
*/
2018-09-10 08:32:49 +00:00
async getlist(req, reply) {
try {
2018-10-05 13:10:59 +00:00
const config = getConfiguration();
2018-10-28 09:43:09 +00:00
2018-12-12 11:23:49 +00:00
const usingCredentials = await this.wzWrapper.usingCredentials();
2018-09-10 08:32:49 +00:00
const isXpackEnabled =
typeof XPACK_RBAC_ENABLED !== 'undefined' &&
XPACK_RBAC_ENABLED &&
2018-12-12 11:23:49 +00:00
usingCredentials;
2018-09-10 08:32:49 +00:00
const isSuperUser =
isXpackEnabled &&
req.auth &&
req.auth.credentials &&
req.auth.credentials.roles &&
req.auth.credentials.roles.includes('superuser');
const data = await this.wzWrapper.getAllIndexPatterns();
2018-12-18 15:19:05 +00:00
if ((((data || {}).hits || {}).hits || []).length === 0)
2018-09-10 08:32:49 +00:00
throw new Error('There is no index pattern');
2018-12-18 15:19:05 +00:00
if (((data || {}).hits || {}).hits) {
2018-10-05 13:10:59 +00:00
let list = this.validateIndexPattern(data.hits.hits);
2018-10-28 09:43:09 +00:00
if (
config &&
config['ip.ignore'] &&
Array.isArray(config['ip.ignore']) &&
config['ip.ignore'].length
) {
list = list.filter(
item =>
item && item.title && !config['ip.ignore'].includes(item.title)
);
}
2018-09-10 08:32:49 +00:00
return reply({
data:
isXpackEnabled && !isSuperUser
? await this.filterAllowedIndexPatternList(list, req)
: list
});
}
throw new Error(
"The Elasticsearch request didn't fetch the expected data"
);
} catch (error) {
2018-10-01 07:56:50 +00:00
log('GET /elastic/index-patterns', error.message || error);
2018-09-10 08:32:49 +00:00
return ErrorResponse(error.message || error, 4006, 500, reply);
}
2018-09-10 08:32:49 +00:00
}
/**
* Replaces visualizations main fields to fit a certain pattern.
* @param {Array<Object>} app_objects Object containing raw visualizations.
* @param {String} id Index-pattern id to use in the visualizations. Eg: 'wazuh-alerts'
*/
buildVisualizationsRaw(app_objects, id) {
try {
const config = getConfiguration();
const monitoringPattern =
(config || {})['wazuh.monitoring.pattern'] || 'wazuh-monitoring-3.x-*';
2018-09-10 08:32:49 +00:00
const visArray = [];
let aux_source, bulk_content;
for (let element of app_objects) {
2018-12-13 10:02:53 +00:00
aux_source = JSON.parse(JSON.stringify(element._source));
2018-12-03 11:07:18 +00:00
// Replace index-pattern for visualizations
2018-12-13 10:02:53 +00:00
if (
aux_source &&
aux_source.kibanaSavedObjectMeta &&
aux_source.kibanaSavedObjectMeta.searchSourceJSON &&
typeof aux_source.kibanaSavedObjectMeta.searchSourceJSON === 'string'
) {
const defaultStr = aux_source.kibanaSavedObjectMeta.searchSourceJSON;
defaultStr.includes('wazuh-monitoring')
? (aux_source.kibanaSavedObjectMeta.searchSourceJSON = defaultStr.replace(
'wazuh-monitoring',
monitoringPattern[monitoringPattern.length - 1] === '*'
? monitoringPattern
: monitoringPattern + '*'
))
: (aux_source.kibanaSavedObjectMeta.searchSourceJSON = defaultStr.replace(
'wazuh-alerts',
id
));
2018-12-03 11:07:18 +00:00
}
2018-09-10 08:32:49 +00:00
2018-12-03 11:07:18 +00:00
// Replace index-pattern for selector visualizations
2018-12-13 10:02:53 +00:00
if (
aux_source &&
aux_source.visState &&
aux_source.visState &&
typeof aux_source.visState === 'string'
) {
aux_source.visState = aux_source.visState.replace('wazuh-alerts', id);
2018-12-03 11:07:18 +00:00
}
2018-09-10 08:32:49 +00:00
// Bulk source
bulk_content = {};
bulk_content[element._type] = aux_source;
visArray.push({
attributes: bulk_content.visualization,
type: element._type,
id: element._id,
_version: bulk_content.visualization.version
});
}
return visArray;
} catch (error) {
return Promise.reject(error);
}
2018-09-10 08:32:49 +00:00
}
/**
* Replaces cluster visualizations main fields.
* @param {Array<Object>} app_objects Object containing raw visualizations.
* @param {String} id Index-pattern id to use in the visualizations. Eg: 'wazuh-alerts'
* @param {Array<String>} nodes Array of node names. Eg: ['node01', 'node02']
* @param {String} name Cluster name. Eg: 'wazuh'
* @param {String} master_node Master node name. Eg: 'node01'
*/
buildClusterVisualizationsRaw(
app_objects,
id,
nodes = [],
name,
master_node,
pattern_name = '*'
) {
try {
const visArray = [];
let aux_source, bulk_content;
for (const element of app_objects) {
// Stringify and replace index-pattern for visualizations
aux_source = JSON.stringify(element._source);
aux_source = aux_source.replace('wazuh-alerts', id);
aux_source = JSON.parse(aux_source);
// Bulk source
bulk_content = {};
bulk_content[element._type] = aux_source;
const visState = JSON.parse(bulk_content.visualization.visState);
const title = visState.title;
if (visState.type && visState.type === 'timelion') {
let query = '';
if (title === 'Wazuh App Cluster Overview') {
for (const node of nodes) {
query += `.es(index=${pattern_name},q="cluster.name: ${name} AND cluster.node: ${
node.name
2018-12-13 10:02:53 +00:00
}").label("${node.name}"),`;
}
2018-09-10 08:32:49 +00:00
query = query.substring(0, query.length - 1);
} else if (title === 'Wazuh App Cluster Overview Manager') {
query += `.es(index=${pattern_name},q="cluster.name: ${name}").label("${name} cluster")`;
}
2018-09-10 08:32:49 +00:00
visState.params.expression = query;
bulk_content.visualization.visState = JSON.stringify(visState);
}
2018-09-10 08:32:49 +00:00
visArray.push({
attributes: bulk_content.visualization,
type: element._type,
id: element._id,
_version: bulk_content.visualization.version
});
}
return visArray;
} catch (error) {
return Promise.reject(error);
}
2018-09-10 08:32:49 +00:00
}
/**
* This creates a visualization of data in req
2018-12-13 10:02:53 +00:00
* @param {Object} req
* @param {Object} reply
* @returns {Object} vis obj or ErrorResponse
*/
2018-09-10 08:32:49 +00:00
async createVis(req, reply) {
try {
if (
!req.params.pattern ||
!req.params.tab ||
(req.params.tab &&
!req.params.tab.includes('overview-') &&
!req.params.tab.includes('agents-'))
) {
throw new Error('Missing parameters creating visualizations');
}
const tabPrefix = req.params.tab.includes('overview')
? 'overview'
: 'agents';
const tabSplit = req.params.tab.split('-');
const tabSufix = tabSplit[1];
const file =
tabPrefix === 'overview'
? OverviewVisualizations[tabSufix]
: AgentsVisualizations[tabSufix];
const raw = await this.buildVisualizationsRaw(file, req.params.pattern);
return reply({ acknowledge: true, raw: raw });
} catch (error) {
return ErrorResponse(error.message || error, 4007, 500, reply);
}
2018-09-10 08:32:49 +00:00
}
/**
2018-12-13 10:02:53 +00:00
* This creates a visualization of cluster
* @param {Object} req
* @param {Object} reply
* @returns {Object} vis obj or ErrorResponse
*/
2018-09-10 08:32:49 +00:00
async createClusterVis(req, reply) {
try {
if (
!req.params.pattern ||
!req.params.tab ||
!req.payload ||
!req.payload.nodes ||
!req.payload.nodes.items ||
!req.payload.nodes.name ||
(req.params.tab && !req.params.tab.includes('cluster-'))
) {
throw new Error('Missing parameters creating visualizations');
}
const file = ClusterVisualizations['monitoring'];
const nodes = req.payload.nodes.items;
const name = req.payload.nodes.name;
const master_node = req.payload.nodes.master_node;
const pattern_doc = await this.wzWrapper.getIndexPatternUsingGet(
req.params.pattern
);
const pattern_name = pattern_doc._source['index-pattern'].title;
const raw = await this.buildClusterVisualizationsRaw(
file,
req.params.pattern,
nodes,
name,
master_node,
pattern_name
);
return reply({ acknowledge: true, raw: raw });
} catch (error) {
return ErrorResponse(error.message || error, 4009, 500, reply);
}
2018-09-10 08:32:49 +00:00
}
/**
* Reload elastic index
2018-12-13 10:02:53 +00:00
* @param {Object} req
* @param {Object} reply
* @returns {Object} status obj or ErrorResponse
*/
2018-09-10 08:32:49 +00:00
async refreshIndex(req, reply) {
try {
if (!req.params.pattern) throw new Error('Missing parameters');
2018-09-10 08:32:49 +00:00
const output = await this.wzWrapper.updateIndexPatternKnownFields(
req.params.pattern
);
2018-09-10 08:32:49 +00:00
return reply({ acknowledge: true, output: output });
} catch (error) {
2018-10-01 07:56:50 +00:00
log('GET /elastic/known-fields/{pattern}', error.message || error);
2018-09-10 08:32:49 +00:00
return ErrorResponse(error.message || error, 4008, 500, reply);
}
2018-09-10 08:32:49 +00:00
}
2018-12-03 13:54:39 +00:00
/**
* This returns de the alerts of an angent
2018-12-03 15:07:59 +00:00
* @param {*} req
2018-12-03 13:54:39 +00:00
* POST /elastic/alerts
* {
* "agent.id": 100 ,
* "cluster.name": "wazuh",
* "date.from": "now-1d/timestamp/standard date", // Like Elasticsearch does
* "date.to": "now/timestamp/standard date", // Like Elasticsearch does
* "rule.group": ["onegroup", "anothergroup"] // Or empty array [ ]
* "size": 5 // Optional parameter
* }
2018-12-03 15:07:59 +00:00
*
* @param {*} reply
* {alerts: [...]} or ErrorResponse
2018-12-03 13:54:39 +00:00
*/
2018-12-03 15:07:59 +00:00
async alerts(req, reply) {
2018-12-03 13:54:39 +00:00
try {
2018-12-03 15:07:59 +00:00
const pattern = req.payload.pattern || 'wazuh-alerts-3.x-*';
const from = req.payload.from || 'now-1d';
const to = req.payload.to || 'now';
const size = req.payload.size || 10;
const payload = Base(pattern, [], from, to);
payload.query = { bool: { must: [] } };
const agent = req.payload['agent.id'];
const manager = req.payload['manager.name'];
const cluster = req.payload['cluster.name'];
2018-12-04 08:38:18 +00:00
const rulegGroups = req.payload['rule.groups'];
2018-12-03 15:07:59 +00:00
if (agent)
payload.query.bool.must.push({
match: { 'agent.id': agent }
});
if (cluster)
payload.query.bool.must.push({
match: { 'cluster.name': cluster }
});
if (manager)
payload.query.bool.must.push({
match: { 'manager.name': manager }
});
2018-12-04 08:38:18 +00:00
if (rulegGroups)
payload.query.bool.must.push({
match: { 'rule.groups': rulegGroups }
});
2018-12-03 15:07:59 +00:00
payload.size = size;
payload.docvalue_fields = [
'@timestamp',
'cluster.name',
'manager.name',
'agent.id',
'rule.id',
'rule.group',
'rule.description'
];
const data = await this.wzWrapper.searchWazuhAlertsWithPayload(payload);
return reply({ alerts: data.hits.hits });
2018-12-03 13:54:39 +00:00
} catch (error) {
log('POST /elastic/alerts', error.message || error);
return ErrorResponse(error.message || error, 4010, 500, reply);
}
}
}