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

528 lines
18 KiB
JavaScript

const importAppObjects = require('../initialize');
const fs = require('fs');
const yml = require('js-yaml');
const path = require('path');
module.exports = (server, options) => {
// Elastic JS Client
const elasticRequest = server.plugins.elasticsearch.getCluster('data');
const getTimeStamp = async (req,reply) => {
try {
const data = await elasticRequest.callWithInternalUser('search', {
index: '.wazuh-version',
type : 'wazuh-version'
})
if(data.hits &&
data.hits.hits[0] &&
data.hits.hits[0]._source &&
data.hits.hits[0]._source.installationDate &&
data.hits.hits[0]._source.lastRestart){
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 (err) {
return reply({
statusCode: 500,
error : 99,
message : err.message || 'Could not fetch .wazuh-version index'
}).code(500);
}
}
//Handlers
const fetchElastic = async (req, payload) => {
try {
const data = await elasticRequest.callWithInternalUser('search', {
index: 'wazuh-alerts-3.x-*',
type: 'wazuh',
body: payload
});
return data;
} catch (error) {
return Promise.reject(error);
}
};
const getConfig = async id => {
try {
const data = await elasticRequest.callWithInternalUser('get', {
index: '.wazuh',
type : 'wazuh-configuration',
id
});
return {
user : data._source.api_user,
password : Buffer.from(data._source.api_password, 'base64').toString("ascii"),
url : data._source.url,
port : data._source.api_port,
insecure : data._source.insecure,
cluster_info: data._source.cluster_info,
extensions : data._source.extensions
};
} catch (error){
return { error: 'no elasticsearch', error_code: 2 };
}
};
// Updating Wazuh app visualizations and dashboards
const updateAppObjects = async (req, reply) => {
try {
await elasticRequest.callWithInternalUser('deleteByQuery', {
index: '.kibana',
body : {
query: {
bool: {
must: { match: { 'visualization.title': 'Wazuh App*' } },
must_not: { match: { 'visualization.title': 'Wazuh App Overview General Agents status' } }
}
}
}
})
// Update the pattern in the configuration
importAppObjects(req.params.pattern);
return reply({ statusCode: 200, data: 'Index pattern updated' });
} catch (error) {
return reply({
statusCode: 500,
error : 9,
message : `Could not delete visualizations due to ${error.message || error}`
}).code(500);
}
};
const getTemplate = async (req, reply) => {
try {
const data = await elasticRequest.callWithInternalUser('cat.templates', {})
if (req.params.pattern == "wazuh-alerts-3.x-*" && data.includes("wazuh-alerts-3.*")) {
return reply({
statusCode: 200,
status : true,
data : `Template found for ${req.params.pattern}`
});
} else {
const lastChar = req.params.pattern[req.params.pattern.length -1];
const array = data.match(/[^\s]+/g);
let pattern = req.params.pattern;
if (lastChar === '*') { // Remove last character if it is a '*'
pattern = pattern.slice(0, -1);
}
for (let i = 1; i < array.length; i++) {
if (array[i].includes(pattern) && array[i-1] == `wazuh`) {
return reply({
statusCode: 200,
status : true,
data : `Template found for ${req.params.pattern}`
});
}
}
return reply({
statusCode: 200,
status : false,
data : `No template found for ${req.params.pattern}`
});
}
} catch (error){
return reply({
statusCode: 500,
error : 10000,
message : `Could not retrieve templates from Elasticsearch due to ${error.message || error}`
}).code(500);
}
};
const checkPattern = async (req, reply) => {
try {
const response = await elasticRequest.callWithInternalUser('search', {
index: '.kibana',
body : { size:999, query: { bool: { must: { match: { type: 'index-pattern' } } } } }
})
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({ statusCode: 500, status: false, error:10020, message: 'Index pattern not found' });
} catch (error) {
return reply({
statusCode: 500,
error : 10000,
message : `Something went wrong retrieving index-patterns from Elasticsearch due to ${error.message || error}`
}).code(500);
}
};
const getFieldTop = async (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' }
}
}
}
};
// 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;
const data = await fetchElastic(req, payload);
return (data.hits.total === 0 || typeof data.aggregations['2'].buckets[0] === 'undefined') ?
reply({ statusCode: 200, data: '' }) :
reply({ statusCode: 200, data: data.aggregations['2'].buckets[0].key });
} catch (error) {
return reply({
statusCode: 500,
error : 9,
message : error.message || error
}).code(500);
}
};
const getSetupInfo = async (req, reply) => {
try {
const data = await elasticRequest.callWithInternalUser('search', {
index: '.wazuh-version',
type : 'wazuh-version'
})
return data.hits.total === 0 ?
reply({ statusCode: 200, data: '' }) :
reply({ statusCode: 200, data: data.hits.hits[0]._source });
} catch (error) {
return reply({
statusCode: 500,
error : 9,
message : `Could not get data from elasticsearch due to ${error.message || error}`
}).code(500);
}
};
const getCurrentlyAppliedPattern = async (req, reply) => {
try{
// We search for the currently applied pattern in the visualizations
const data = await elasticRequest .callWithInternalUser('search', {
index: '.kibana',
type : 'doc',
q : `visualization.title:"Wazuh App Overview General Metric alerts"`
})
if(data && data.hits && data.hits.hits && data.hits.hits[0] && data.hits.hits[0]._source){
return reply({
statusCode: 200,
data : JSON.parse(data.hits.hits[0]._source.visualization.kibanaSavedObjectMeta.searchSourceJSON).index
});
}
throw new Error('no_visualization');
} catch (error) {
return (error && error.message && error.message === 'no_visualization') ?
reply('kibana_index_pattern_error').code(500) :
reply('elasticsearch_down').code(500);
}
};
module.exports = getConfig;
const filterAllowedIndexPatternList = async (list,req) => {
let finalList = [];
for(let item of list){
try {
const allow = await elasticRequest.callWithRequest(req,'search', {
index: item.title,
type : 'wazuh'
});
if(allow && allow.hits && allow.hits.total >= 1) finalList.push(item);
} catch (error){
console.log(`Some user trys to fetch the index pattern ${item.title} without permissions`)
}
}
return finalList;
}
const getlist = async (req,res) => {
try {
const xpack = await elasticRequest.callWithInternalUser('cat.plugins', { });
const isXpackEnabled = typeof xpack === 'string' && xpack.includes('x-pack');
const isSuperUser = isXpackEnabled && req.auth.credentials.roles.includes('superuser');
const data = await elasticRequest
.callWithInternalUser('search', {
index: '.kibana',
type: 'doc',
body: {
"query":{
"match":{
"type": "index-pattern"
}
}
}
});
if(data && data.hits && data.hits.hits){
const minimum = ["@timestamp", "full_log", "manager.name", "agent.id"];
let list = [];
if(data.hits.hits.length === 0) throw new Error('There is no index pattern');
for(const index of data.hits.hits){
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
})
}
}
return res({data: isXpackEnabled && !isSuperUser ? await filterAllowedIndexPatternList(list,req) : list});
}
throw new Error('The Elasticsearch request didn\'t fetch the expected data');
} catch(error){
return res({error: error.message || error}).code(500)
}
}
const deleteVis = async (req,res) => {
try {
const tmp = await elasticRequest.callWithInternalUser('deleteByQuery', {
index: '.kibana',
body: {
query: { bool: { must: { match: { 'visualization.description': req.params.timestamp } } } },
size : 9999
}
})
await elasticRequest.callWithInternalUser('indices.refresh', { index: ['.kibana']})
return res({aknowledge: true , output: tmp});
} catch(error){
console.log(error.message || error)
return res({error:error.message || error}).code(500);
}
}
/**
* Replaces our visualizations main fields to fit our pattern needs.
* @param {*} app_objects Object with the visualizations raw content.
* @param {*} id Eg: 'wazuh-alerts'
*/
const buildVisualizationsBulk = (app_objects,id,timestamp) => {
try{
let body = '';
for (let element of app_objects) {
body += '{ "index": { "_index": ".kibana", "_type": "doc", ' + '"_id": "' + element._type + ':' + element._id + '-'+timestamp+'" } }\n';
let temp = {};
let aux = JSON.stringify(element._source);
aux = aux.replace("wazuh-alerts", id);
aux = JSON.parse(aux);
temp[element._type] = aux;
if (temp[element._type].kibanaSavedObjectMeta.searchSourceJSON.index) {
temp[element._type].kibanaSavedObjectMeta.searchSourceJSON.index = id;
}
temp["type"] = element._type;
temp.visualization.description = timestamp;
body += JSON.stringify(temp) + "\n";
}
return body;
} catch (error) {
console.log(error.message || error)
}
}
const createVis = async (req,res) => {
try {
if(!req.params.pattern ||
!req.params.tab ||
!req.params.timestamp ||
(req.params.tab && !req.params.tab.includes('overview') && !req.params.tab.includes('agents'))
) {
throw new Error('Missing parameters');
}
const tabPrefix = req.params.tab.includes('overview') ? 'overview' : 'agents';
const file = require(`../integration-files/visualizations/${tabPrefix}/${req.params.tab}`);
const bulkBody = buildVisualizationsBulk(file,req.params.pattern,req.params.timestamp);
await elasticRequest.callWithInternalUser('bulk', { index: '.kibana', body: bulkBody });
await elasticRequest.callWithInternalUser('indices.refresh', { index: ['.kibana']})
return res({aknowledge: true});
} catch(error){
console.log(error.message || error)
return res({error:error.message || error}).code(500);
}
}
// Get index patterns list
server.route({
method: 'GET',
path: '/get-list',
handler: getlist
});
//Server routes
/*
* GET /api/wazuh-elastic/create-vis/{tab}/{timestamp}/{pattern}
* Create visualizations specified in 'tab' parameter with the 'timestamp' sufix and applying to 'pattern'
*
**/
server.route({
method: 'GET',
path: '/api/wazuh-elastic/create-vis/{tab}/{timestamp}/{pattern}',
handler: createVis
});
/*
* GET /api/wazuh-elastic/delete-vis/{tab}
* Delete visualizations specified in 'tab' parameter
*
**/
server.route({
method: 'GET',
path: '/api/wazuh-elastic/delete-vis/{timestamp}',
handler: deleteVis
});
/*
* GET /api/wazuh-elastic/current-pattern
* Returns the currently applied pattern
*
**/
server.route({
method: 'GET',
path: '/api/wazuh-elastic/current-pattern',
handler: getCurrentlyAppliedPattern
});
/*
* GET /api/wazuh-elastic/template/{pattern}
* Returns whether a correct template is being applied for the index-pattern
*
**/
server.route({
method: 'GET',
path: '/api/wazuh-elastic/template/{pattern}',
handler: getTemplate
});
/*
* GET /api/wazuh-elastic/pattern/{pattern}
* Returns whether the pattern exists or not
*
**/
server.route({
method: 'GET',
path: '/api/wazuh-elastic/pattern/{pattern}',
handler: checkPattern
});
/*
* GET /api/wazuh-elastic/top/{cluster}/{field}/{time?}
* Returns the agent with most alerts
*
**/
server.route({
method: 'GET',
path: '/api/wazuh-elastic/top/{mode}/{cluster}/{field}',
handler: getFieldTop
});
/*
* GET /api/wazuh-elastic/setup
* Return Wazuh Appsetup info
*
**/
server.route({
method: 'GET',
path: '/api/wazuh-elastic/setup',
handler: getSetupInfo
});
/*
* POST /api/wazuh-elastic/updatePattern
* Update the index pattern in the app visualizations
*
**/
server.route({
method: 'GET',
path: '/api/wazuh-elastic/updatePattern/{pattern}',
handler: updateAppObjects
});
server.route({
method: 'GET',
path: '/api/wazuh-elastic/timestamp',
handler: getTimeStamp
});
};