add support for multiple spaces
This commit is contained in:
parent
7988853b57
commit
351c4e4ef4
10 changed files with 498 additions and 92 deletions
|
@ -3,9 +3,9 @@
|
|||
*
|
||||
* Provides block templates for displaying Sigma rule conversion results in Slack
|
||||
*/
|
||||
const logger = require('../utils/logger');
|
||||
const logger = require('../../utils/logger');
|
||||
|
||||
const { getFileName } = require('../utils/file_utils');
|
||||
const { getFileName } = require('../../utils/file_utils');
|
||||
const FILE_NAME = getFileName(__filename);
|
||||
|
||||
/**
|
||||
|
@ -93,11 +93,11 @@ function getConversionResultBlocks(conversionResult) {
|
|||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'send_sigma_rule_to_siem',
|
||||
text: '🚀 Send to Elasticsearch',
|
||||
emoji: true
|
||||
},
|
||||
value: `send_sigma_rule_to_siem_${rule.id}`,
|
||||
action_id: 'send_sigma_rule_to_siem'
|
||||
value: `select_space_for_rule_${rule.id}`,
|
||||
action_id: 'select_space_for_rule'
|
||||
},
|
||||
]
|
||||
});
|
|
@ -4,9 +4,9 @@
|
|||
* Creates Slack Block Kit blocks for displaying Sigma rule explanations
|
||||
*
|
||||
*/
|
||||
const logger = require('../utils/logger');
|
||||
const logger = require('../../utils/logger');
|
||||
|
||||
const { getFileName } = require('../utils/file_utils');
|
||||
const { getFileName } = require('../../utils/file_utils');
|
||||
const FILE_NAME = getFileName(__filename);
|
||||
|
||||
/**
|
|
@ -4,11 +4,10 @@
|
|||
* Generates Slack Block Kit blocks for displaying Sigma rule search results
|
||||
* Includes pagination controls for navigating large result sets
|
||||
*
|
||||
* @author Fylgja Development Team
|
||||
*/
|
||||
const logger = require('../utils/logger');
|
||||
const logger = require('../../utils/logger');
|
||||
|
||||
const { getFileName } = require('../utils/file_utils');
|
||||
const { getFileName } = require('../../utils/file_utils');
|
||||
const FILE_NAME = getFileName(__filename);
|
||||
|
||||
/**
|
127
src/blocks/sigma/sigma_space_selection_block.js
Normal file
127
src/blocks/sigma/sigma_space_selection_block.js
Normal file
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* space_selection_block.js
|
||||
*
|
||||
* Provides block templates for displaying Elasticsearch space selection in Slack
|
||||
*/
|
||||
const logger = require('../../utils/logger');
|
||||
const { getAllSpaces } = require('../../services/elastic/elastic_api_service');
|
||||
|
||||
const { getFileName } = require('../../utils/file_utils');
|
||||
const FILE_NAME = getFileName(__filename);
|
||||
|
||||
/**
|
||||
* Generate blocks for space selection for a specific rule
|
||||
*
|
||||
* @param {string} ruleId - The ID of the rule to send to a space
|
||||
* @param {Object} ruleInfo - Optional rule title and other information
|
||||
* @returns {Array} Array of blocks for Slack message
|
||||
*/
|
||||
function getSpaceSelectionBlocks(ruleId, ruleInfo = {}) {
|
||||
logger.debug(`${FILE_NAME}: Generating space selection blocks for rule: ${ruleId}`);
|
||||
|
||||
// Get all configured spaces
|
||||
const spaces = getAllSpaces();
|
||||
|
||||
if (!spaces || spaces.length === 0) {
|
||||
logger.warn(`${FILE_NAME}: No spaces configured for selection blocks`);
|
||||
return [{
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: 'No Elasticsearch spaces are configured. Please update your configuration.'
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
// Create header block
|
||||
const blocks = [
|
||||
{
|
||||
type: 'header',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: `Select a Space for Rule: ${ruleInfo.title || ruleId}`,
|
||||
emoji: true
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: `Please select which Elasticsearch space you want to send this rule to:`
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
}
|
||||
];
|
||||
|
||||
// Create buttons for each space
|
||||
// If we have many spaces, we need to create multiple action blocks
|
||||
// Slack only allows 5 buttons per actions block
|
||||
const BUTTONS_PER_ACTION = 5;
|
||||
let actionBlocks = [];
|
||||
let currentActionBlock = {
|
||||
type: 'actions',
|
||||
elements: []
|
||||
};
|
||||
|
||||
// Add buttons for each space
|
||||
spaces.forEach((space, index) => {
|
||||
// Create the button for this space
|
||||
const button = {
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: `${space.emoji || ''} ${space.name}`,
|
||||
emoji: true
|
||||
},
|
||||
value: `send_rule_to_space_${ruleId}_${space.id}`,
|
||||
action_id: `send_rule_to_space_${space.id}`
|
||||
};
|
||||
|
||||
// Add button to current action block
|
||||
currentActionBlock.elements.push(button);
|
||||
|
||||
// If we've reached the button limit or this is the last space,
|
||||
// add the current action block to our collection
|
||||
if (currentActionBlock.elements.length === BUTTONS_PER_ACTION || index === spaces.length - 1) {
|
||||
actionBlocks.push(currentActionBlock);
|
||||
|
||||
// Start a new action block if we have more spaces
|
||||
if (index < spaces.length - 1) {
|
||||
currentActionBlock = {
|
||||
type: 'actions',
|
||||
elements: []
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add all the action blocks to our blocks array
|
||||
blocks.push(...actionBlocks);
|
||||
|
||||
// Add a cancel button
|
||||
blocks.push({
|
||||
type: 'actions',
|
||||
elements: [
|
||||
{
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'Cancel',
|
||||
emoji: true
|
||||
},
|
||||
style: 'danger',
|
||||
value: `cancel_space_selection`,
|
||||
action_id: 'cancel_space_selection'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
logger.debug(`${FILE_NAME}: Generated ${blocks.length} blocks for space selection`);
|
||||
return blocks;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getSpaceSelectionBlocks
|
||||
};
|
|
@ -3,9 +3,9 @@
|
|||
*
|
||||
* Creates Slack Block Kit blocks for displaying Sigma rule database statistics
|
||||
*/
|
||||
const logger = require('../utils/logger');
|
||||
const logger = require('../../utils/logger');
|
||||
|
||||
const { getFileName } = require('../utils/file_utils');
|
||||
const { getFileName } = require('../../utils/file_utils');
|
||||
const FILE_NAME = getFileName(__filename);
|
||||
|
||||
/**
|
|
@ -4,9 +4,9 @@
|
|||
* Creates Slack Block Kit blocks for displaying Sigma rule YAML content
|
||||
*
|
||||
*/
|
||||
const logger = require('../utils/logger');
|
||||
const logger = require('../../utils/logger');
|
||||
|
||||
const { getFileName } = require('../utils/file_utils');
|
||||
const { getFileName } = require('../../utils/file_utils');
|
||||
const FILE_NAME = getFileName(__filename);
|
||||
|
||||
/**
|
|
@ -1,3 +1,8 @@
|
|||
/*
|
||||
* appConfig.js
|
||||
*
|
||||
* retrives configuration data from fylgja.yml file
|
||||
*/
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const yaml = require('js-yaml');
|
||||
|
@ -60,10 +65,19 @@ module.exports = {
|
|||
|
||||
// Elasticsearch configuration from YAML
|
||||
ELASTICSEARCH_CONFIG: {
|
||||
apiEndpoint: yamlConfig?.elastic?.['api-endpoint'] ||
|
||||
"http://localhost:5601/api/detection_engine/rules",
|
||||
credentials: yamlConfig?.elastic?.['elastic-authentication-credentials'] ||
|
||||
"elastic:changeme"
|
||||
protocol: yamlConfig?.elasticsearch?.protocol || "http",
|
||||
hosts: yamlConfig?.elasticsearch?.hosts || ["localhost:9200"],
|
||||
username: yamlConfig?.elasticsearch?.username || process.env.ELASTIC_USERNAME || "elastic",
|
||||
password: yamlConfig?.elasticsearch?.password || process.env.ELASTIC_PASSWORD || "changeme",
|
||||
apiEndpoint: yamlConfig?.elasticsearch?.api_endpoint || "http://localhost:5601/api/detection_engine/rules",
|
||||
spaces: yamlConfig?.elasticsearch?.spaces || [
|
||||
{
|
||||
name: "Default",
|
||||
id: "default",
|
||||
indexPattern: "logs-*",
|
||||
emoji: "🔍"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Logging configuration from YAML
|
||||
|
|
|
@ -8,11 +8,13 @@ const { handleError } = require('../../utils/error_handler');
|
|||
const { explainSigmaRule, getSigmaRuleYaml } = require('../../services/sigma/sigma_details_service');
|
||||
const { convertRuleToBackend } = require('../../services/sigma/sigma_backend_converter');
|
||||
const { searchSigmaRules } = require('../../services/sigma/sigma_search_service');
|
||||
const { getYamlViewBlocks } = require('../../blocks/sigma_view_yaml_block');
|
||||
const { getSearchResultBlocks } = require('../../blocks/sigma_search_results_block');
|
||||
const { getConversionResultBlocks } = require('../../blocks/sigma_conversion_block');
|
||||
const { getRuleExplanationBlocks } = require('../../blocks/sigma_details_block');
|
||||
const { getYamlViewBlocks } = require('../../blocks/sigma/sigma_view_yaml_block');
|
||||
const { getSearchResultBlocks } = require('../../blocks/sigma/sigma_search_results_block');
|
||||
const { getConversionResultBlocks } = require('../../blocks/sigma/sigma_conversion_block');
|
||||
const { getRuleExplanationBlocks } = require('../../blocks/sigma/sigma_details_block');
|
||||
const { sendRuleToSiem } = require('../../services/elastic/elastic_api_service');
|
||||
const { getSpaceSelectionBlocks } = require('../../blocks/sigma/sigma_space_selection_block');
|
||||
const { getAllSpaces } = require('../../services/elastic/elastic_api_service');
|
||||
|
||||
const { SIGMA_CLI_CONFIG, ELASTICSEARCH_CONFIG } = require('../../config/appConfig');
|
||||
|
||||
|
@ -543,8 +545,214 @@ const registerActionHandlers = (app) => {
|
|||
});
|
||||
|
||||
logger.info(`${FILE_NAME}: All sigma action handlers registered successfully`);
|
||||
|
||||
// Handle space selection button click
|
||||
app.action('select_space_for_rule', async ({ body, ack, respond }) => {
|
||||
try {
|
||||
await ack();
|
||||
logger.debug(`${FILE_NAME}: select_space_for_rule action received: ${JSON.stringify(body.actions)}`);
|
||||
|
||||
if (!body || !body.actions || !body.actions[0] || !body.actions[0].value) {
|
||||
logger.error(`${FILE_NAME}: Invalid action payload: missing rule ID`);
|
||||
await respond({
|
||||
text: 'Error: Could not determine which rule to select space for',
|
||||
replace_original: false,
|
||||
response_type: 'ephemeral'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract rule ID from value
|
||||
const actionValue = body.actions[0].value;
|
||||
const ruleId = actionValue.replace('select_space_for_rule_', '');
|
||||
|
||||
// Get rule information to display in the space selection message
|
||||
const explainResult = await explainSigmaRule(ruleId);
|
||||
const ruleInfo = explainResult.success ? explainResult.explanation : { title: ruleId };
|
||||
|
||||
// Generate blocks for space selection
|
||||
const blocks = getSpaceSelectionBlocks(ruleId, ruleInfo);
|
||||
|
||||
// Show space selection options
|
||||
await respond({
|
||||
blocks: blocks,
|
||||
replace_original: false,
|
||||
response_type: 'ephemeral'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
await handleError(error, `${FILE_NAME}: select_space_for_rule action`, respond, {
|
||||
replaceOriginal: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle space selection cancel button
|
||||
app.action('cancel_space_selection', async ({ body, ack, respond }) => {
|
||||
try {
|
||||
await ack();
|
||||
await respond({
|
||||
text: 'Space selection cancelled.',
|
||||
replace_original: false,
|
||||
response_type: 'ephemeral'
|
||||
});
|
||||
} catch (error) {
|
||||
await handleError(error, `${FILE_NAME}: cancel_space_selection action`, respond, {
|
||||
replaceOriginal: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Dynamic handler for all space selection buttons
|
||||
// This uses a pattern matcher to match any action ID that starts with "send_rule_to_space_"
|
||||
app.action(/^send_rule_to_space_(.*)$/, async ({ body, action, ack, respond }) => {
|
||||
try {
|
||||
await ack();
|
||||
logger.debug(`${FILE_NAME}: Space selection action received: ${JSON.stringify(action)}`);
|
||||
|
||||
// Extract rule ID and space ID from the action value
|
||||
const actionValue = action.value;
|
||||
const parts = actionValue.split('_');
|
||||
const spaceId = parts.pop(); // Last part is the space ID
|
||||
const ruleId = actionValue.match(/send_rule_to_space_(.+)_/)[1]; // Extract full UUID
|
||||
|
||||
logger.info(`${FILE_NAME}: Selected space ${spaceId} for rule ${ruleId}`);
|
||||
|
||||
|
||||
// Get space info
|
||||
const spaces = getAllSpaces();
|
||||
const selectedSpace = spaces.find(s => s.id === spaceId);
|
||||
|
||||
if (!selectedSpace) {
|
||||
logger.error(`${FILE_NAME}: Space not found: ${spaceId}`);
|
||||
await respond({
|
||||
text: `Error: Space "${spaceId}" not found in configuration`,
|
||||
replace_original: false,
|
||||
response_type: 'ephemeral'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Inform user that processing is happening
|
||||
await respond({
|
||||
text: `Sending rule ${ruleId} to ${selectedSpace.emoji || ''} ${selectedSpace.name} space...`,
|
||||
replace_original: false,
|
||||
response_type: 'ephemeral'
|
||||
});
|
||||
|
||||
// Get the converted rule in Elasticsearch format
|
||||
const config = {
|
||||
backend: SIGMA_CLI_CONFIG.backend,
|
||||
target: SIGMA_CLI_CONFIG.target,
|
||||
format: SIGMA_CLI_CONFIG.format
|
||||
};
|
||||
|
||||
logger.info(`${FILE_NAME}: Converting rule ${ruleId} for SIEM export to space ${spaceId}`);
|
||||
const conversionResult = await convertRuleToBackend(ruleId, config);
|
||||
|
||||
if (!conversionResult.success) {
|
||||
logger.error(`${FILE_NAME}: Rule conversion failed: ${conversionResult.message}`);
|
||||
await respond({
|
||||
text: `Error: Failed to convert rule for SIEM: ${conversionResult.message}`,
|
||||
replace_original: false,
|
||||
response_type: 'ephemeral'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the converted rule JSON
|
||||
let rulePayload;
|
||||
try {
|
||||
rulePayload = JSON.parse(conversionResult.output);
|
||||
|
||||
// Add required fields if not present
|
||||
rulePayload.rule_id = rulePayload.rule_id || ruleId;
|
||||
rulePayload.from = rulePayload.from || "now-360s";
|
||||
rulePayload.to = rulePayload.to || "now";
|
||||
rulePayload.interval = rulePayload.interval || "5m";
|
||||
|
||||
// Set index pattern from space configuration if available
|
||||
if (selectedSpace.indexPattern) {
|
||||
rulePayload.index = Array.isArray(selectedSpace.indexPattern)
|
||||
? selectedSpace.indexPattern
|
||||
: [selectedSpace.indexPattern];
|
||||
logger.debug(`${FILE_NAME}: Setting index pattern from space config: ${JSON.stringify(rulePayload.index)}`);
|
||||
}
|
||||
|
||||
// Make sure required fields are present
|
||||
if (!rulePayload.name) {
|
||||
rulePayload.name = conversionResult.rule?.title || `Sigma Rule ${ruleId}`;
|
||||
}
|
||||
|
||||
if (!rulePayload.description) {
|
||||
rulePayload.description = conversionResult.rule?.description ||
|
||||
`Converted from Sigma rule: ${ruleId}`;
|
||||
}
|
||||
|
||||
if (!rulePayload.risk_score) {
|
||||
// Map Sigma level to risk score
|
||||
const levelMap = {
|
||||
'critical': 90,
|
||||
'high': 73,
|
||||
'medium': 50,
|
||||
'low': 25,
|
||||
'informational': 10
|
||||
};
|
||||
|
||||
rulePayload.risk_score = levelMap[conversionResult.rule?.level] || 50;
|
||||
}
|
||||
|
||||
if (!rulePayload.severity) {
|
||||
rulePayload.severity = conversionResult.rule?.level || 'medium';
|
||||
}
|
||||
|
||||
if (!rulePayload.enabled) {
|
||||
rulePayload.enabled = true;
|
||||
}
|
||||
|
||||
} catch (parseError) {
|
||||
logger.error(`${FILE_NAME}: Failed to parse converted rule JSON: ${parseError.message}`);
|
||||
await respond({
|
||||
text: `Error: The converted rule is not valid JSON: ${parseError.message}`,
|
||||
replace_original: false,
|
||||
response_type: 'ephemeral'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the rule to the selected Elasticsearch space
|
||||
try {
|
||||
const result = await sendRuleToSiem(rulePayload, spaceId);
|
||||
|
||||
if (result.success) {
|
||||
logger.info(`${FILE_NAME}: Successfully sent rule ${ruleId} to space ${spaceId}`);
|
||||
await respond({
|
||||
text: `✅ Success! Rule "${rulePayload.name}" has been added to the ${selectedSpace.emoji || ''} ${selectedSpace.name} space in Elasticsearch.`,
|
||||
replace_original: false,
|
||||
response_type: 'in_channel'
|
||||
});
|
||||
} else {
|
||||
logger.error(`${FILE_NAME}: Error sending rule to SIEM: ${result.message}`);
|
||||
await respond({
|
||||
text: `Error: Failed to add rule to the ${selectedSpace.name} space: ${result.message}`,
|
||||
replace_original: false,
|
||||
response_type: 'ephemeral'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError(error, `${FILE_NAME}: send_rule_to_space action`, respond, {
|
||||
replaceOriginal: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
await handleError(error, `${FILE_NAME}: send_rule_to_space action`, respond, {
|
||||
replaceOriginal: false
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
registerActionHandlers,
|
||||
processRuleDetails,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
const { searchSigmaRules } = require('../../services/sigma/sigma_search_service');
|
||||
const logger = require('../../utils/logger');
|
||||
const { handleError } = require('../../utils/error_handler');
|
||||
const { getSearchResultBlocks } = require('../../blocks/sigma_search_results_block');
|
||||
const { getSearchResultBlocks } = require('../../blocks/sigma/sigma_search_results_block');
|
||||
|
||||
const { getFileName } = require('../../utils/file_utils');
|
||||
const FILE_NAME = getFileName(__filename);
|
||||
|
|
|
@ -12,32 +12,76 @@ const FILE_NAME = 'elastic_api_service.js';
|
|||
/**
|
||||
* Get Elasticsearch configuration with credentials
|
||||
*
|
||||
* @param {string} spaceId - Optional space ID to get configuration for
|
||||
* @returns {Object} Configuration object with URL and credentials
|
||||
*/
|
||||
const getElasticConfig = () => {
|
||||
return {
|
||||
url: ELASTICSEARCH_CONFIG.apiEndpoint.split('/api/')[0] || process.env.ELASTIC_URL,
|
||||
username: ELASTICSEARCH_CONFIG.credentials.split(':')[0] || process.env.ELASTIC_USERNAME,
|
||||
password: ELASTICSEARCH_CONFIG.credentials.split(':')[1] || process.env.ELASTIC_PASSWORD,
|
||||
const getElasticConfig = (spaceId = null) => {
|
||||
// Default config
|
||||
const config = {
|
||||
protocol: ELASTICSEARCH_CONFIG.protocol || "http",
|
||||
hosts: ELASTICSEARCH_CONFIG.hosts || ["localhost:9200"],
|
||||
username: ELASTICSEARCH_CONFIG.username || process.env.ELASTIC_USERNAME,
|
||||
password: ELASTICSEARCH_CONFIG.password || process.env.ELASTIC_PASSWORD,
|
||||
apiEndpoint: ELASTICSEARCH_CONFIG.apiEndpoint
|
||||
};
|
||||
|
||||
// If space ID provided, find the specific space
|
||||
if (spaceId) {
|
||||
const space = ELASTICSEARCH_CONFIG.spaces.find(s => s.id === spaceId);
|
||||
if (space) {
|
||||
// Apply space-specific configuration overrides if they exist
|
||||
return {
|
||||
...config,
|
||||
space: space
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a rule to Elasticsearch SIEM
|
||||
* Get all configured Elasticsearch spaces
|
||||
*
|
||||
* @returns {Array} List of all configured spaces
|
||||
*/
|
||||
const getAllSpaces = () => {
|
||||
return ELASTICSEARCH_CONFIG.spaces || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a rule to Elasticsearch SIEM in a specific space
|
||||
*
|
||||
* @param {Object} rulePayload - The rule payload to send to Elasticsearch
|
||||
* @param {string} spaceId - The ID of the space to send the rule to
|
||||
* @returns {Promise<Object>} - Object containing success status and response/error information
|
||||
*/
|
||||
const sendRuleToSiem = async (rulePayload) => {
|
||||
logger.info(`${FILE_NAME}: Sending rule to Elasticsearch SIEM`);
|
||||
const sendRuleToSiem = async (rulePayload, spaceId = 'default') => {
|
||||
logger.info(`${FILE_NAME}: Sending rule to Elasticsearch SIEM in space: ${spaceId}`);
|
||||
|
||||
try {
|
||||
const elasticConfig = getElasticConfig();
|
||||
const apiUrl = elasticConfig.apiEndpoint;
|
||||
const elasticConfig = getElasticConfig(spaceId);
|
||||
const baseApiUrl = elasticConfig.apiEndpoint;
|
||||
|
||||
// Construct space-specific URL if needed
|
||||
let apiUrl = baseApiUrl;
|
||||
if (spaceId && spaceId !== 'default') {
|
||||
// Insert space ID into URL: http://localhost:5601/api/detection_engine/rules
|
||||
// becomes http://localhost:5601/s/space-id/api/detection_engine/rules
|
||||
const urlParts = baseApiUrl.split('/api/');
|
||||
apiUrl = `${urlParts[0]}/s/${spaceId}/api/${urlParts[1]}`;
|
||||
}
|
||||
|
||||
logger.debug(`${FILE_NAME}: Using Elasticsearch API URL: ${apiUrl}`);
|
||||
|
||||
// Add index pattern to rule if provided by space config
|
||||
if (elasticConfig.space && elasticConfig.space.indexPattern && !rulePayload.index) {
|
||||
rulePayload.index = Array.isArray(elasticConfig.space.indexPattern)
|
||||
? elasticConfig.space.indexPattern
|
||||
: [elasticConfig.space.indexPattern];
|
||||
logger.debug(`${FILE_NAME}: Adding index pattern to rule: ${JSON.stringify(rulePayload.index)}`);
|
||||
}
|
||||
|
||||
// Send the request to Elasticsearch
|
||||
const response = await axios({
|
||||
method: 'post',
|
||||
|
@ -55,18 +99,19 @@ const sendRuleToSiem = async (rulePayload) => {
|
|||
|
||||
// Process the response
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
logger.info(`${FILE_NAME}: Successfully sent rule to SIEM`);
|
||||
logger.info(`${FILE_NAME}: Successfully sent rule to SIEM in space: ${spaceId}`);
|
||||
return {
|
||||
success: true,
|
||||
status: response.status,
|
||||
data: response.data
|
||||
data: response.data,
|
||||
space: elasticConfig.space
|
||||
};
|
||||
} else {
|
||||
logger.error(`${FILE_NAME}: Error sending rule to SIEM. Status: ${response.status}, Response: ${JSON.stringify(response.data)}`);
|
||||
return {
|
||||
success: false,
|
||||
status: response.status,
|
||||
message: `Failed to add rule to SIEM. Status: ${response.status}`,
|
||||
message: `Failed to add rule to SIEM in space ${spaceId}. Status: ${response.status}`,
|
||||
data: response.data
|
||||
};
|
||||
}
|
||||
|
@ -92,6 +137,7 @@ const sendRuleToSiem = async (rulePayload) => {
|
|||
* @param {Object} options - Request options
|
||||
* @param {string} options.method - HTTP method (get, post, put, delete)
|
||||
* @param {string} options.endpoint - API endpoint (appended to base URL)
|
||||
* @param {string} options.spaceId - Optional space ID
|
||||
* @param {Object} options.data - Request payload
|
||||
* @param {Object} options.params - URL parameters
|
||||
* @param {Object} options.headers - Additional headers
|
||||
|
@ -99,14 +145,24 @@ const sendRuleToSiem = async (rulePayload) => {
|
|||
*/
|
||||
const makeElasticRequest = async (options) => {
|
||||
try {
|
||||
const elasticConfig = getElasticConfig();
|
||||
const baseUrl = elasticConfig.url;
|
||||
const elasticConfig = getElasticConfig(options.spaceId);
|
||||
const baseUrl = elasticConfig.protocol + '://' + elasticConfig.hosts[0];
|
||||
|
||||
// Build the full URL - use provided endpoint or default API endpoint
|
||||
const url = options.endpoint ?
|
||||
let url = options.endpoint ?
|
||||
`${baseUrl}${options.endpoint.startsWith('/') ? '' : '/'}${options.endpoint}` :
|
||||
elasticConfig.apiEndpoint;
|
||||
|
||||
// Handle space in URL if needed
|
||||
if (options.spaceId && options.spaceId !== 'default') {
|
||||
// Insert space ID into URL: http://localhost:5601/api/detection_engine/rules
|
||||
// becomes http://localhost:5601/s/space-id/api/detection_engine/rules
|
||||
if (url.includes('/api/')) {
|
||||
const urlParts = url.split('/api/');
|
||||
url = `${urlParts[0]}/s/${options.spaceId}/api/${urlParts[1]}`;
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`${FILE_NAME}: Making ${options.method} request to: ${url}`);
|
||||
|
||||
// Set up default headers
|
||||
|
@ -133,7 +189,8 @@ const makeElasticRequest = async (options) => {
|
|||
return {
|
||||
success: response.status >= 200 && response.status < 300,
|
||||
status: response.status,
|
||||
data: response.data
|
||||
data: response.data,
|
||||
space: elasticConfig.space
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`${FILE_NAME}: Error in Elasticsearch API request: ${error.message}`);
|
||||
|
@ -150,5 +207,6 @@ const makeElasticRequest = async (options) => {
|
|||
module.exports = {
|
||||
sendRuleToSiem,
|
||||
makeElasticRequest,
|
||||
getElasticConfig
|
||||
getElasticConfig,
|
||||
getAllSpaces
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue