Merge pull request #1459 from satti-hari-krishna-reddy/new-sigma

sigma detection ui
This commit is contained in:
Frikky
2024-07-31 19:34:02 +02:00
committed by GitHub
9 changed files with 1482 additions and 294 deletions
+6
View File
@@ -15,6 +15,7 @@ import HealthPage from "./components/HealthPage.jsx";
import theme from "./theme";
import Apps from "./views/Apps";
import AppCreator from "./views/AppCreator";
import DetectionDashBoard from "./views/DetectionDashboard.jsx";
import Welcome from "./views/Welcome.jsx";
import Dashboard from "./views/Dashboard.jsx";
@@ -414,6 +415,11 @@ const App = (message, props) => {
/>
}
/>
<Route
exact
path="/detections/sigma"
element={<DetectionDashBoard globalUrl={globalUrl} />}
/>
<Route
exact
path="/workflows"
+294 -172
View File
@@ -535,6 +535,8 @@ const AngularWorkflow = (defaultprops) => {
const [listCache, setListCache] = React.useState([]);
const [selectedOption, setSelectedOption] = React.useState("");
const [tenzirConfigModalOpen, setTenzirConfigModalOpen] = React.useState(false);
const [rules, setRules] = React.useState([]);
const [sigmaFilesNames, setSigmaFileNames] = React.useState("")
const [distributedFromParent, setDistributedFromParent] = React.useState("")
const [suborgWorkflows, setSuborgWorkflows] = React.useState([])
@@ -1438,7 +1440,7 @@ const releaseToConnectLabel = "Release to Connect"
});
};
const handleKafkaSubmit = (trigger) => {
const handleCommandSubmit = (trigger) => {
if (trigger.trigger_type !== "PIPELINE") {
toast("Unable to save the configuration");
return;
@@ -1446,18 +1448,48 @@ const releaseToConnectLabel = "Release to Connect"
trigger.parameters = []
const topic = document.getElementById('topic')?.value;
const bootstrapServers = document.getElementById('bootstrap_servers')?.value;
const groupId = document.getElementById('group_id')?.value;
//const autoOffsetReset = document.getElementById('auto_offset_reset')?.value;
const command = document.getElementById('sigma')?.value
if(command) {
trigger.parameters.push({
name: "command",
value: command
})
} else {
toast("Please enter the comamnd");
return;
}
// if (autoOffsetReset) {
// trigger.parameters.push({
// name: "auto_offset_reset",
// value: autoOffsetReset
// });
// }
setTenzirConfigModalOpen(false);
};
const handleSubmit = (trigger) => {
if (trigger.trigger_type !== "PIPELINE") {
toast("Unable to save the configuration");
return;
}
if (selectedOption === "Kafka Queue") {
trigger.parameters = []
const topic = document.getElementById('topic')?.value
const bootstrapServers = document.getElementById('bootstrap_servers')?.value
const groupId = document.getElementById('group_id')?.value
const autoOffsetReset = document.getElementById('auto_offset_reset')?.value;
if(topic) {
trigger.parameters.push({
name: "topic",
value: topic
});
})
} else {
toast("please enter the topic name");
toast("Please enter the topic name");
return;
}
@@ -1478,16 +1510,33 @@ const releaseToConnectLabel = "Release to Connect"
});
}
// if (autoOffsetReset) {
// trigger.parameters.push({
// name: "auto_offset_reset",
// value: autoOffsetReset
// });
// }
if (autoOffsetReset) {
trigger.parameters.push({
name: "auto_offset_reset",
value: autoOffsetReset
});
}
setTenzirConfigModalOpen(false);
} else if (selectedOption === "Syslog listener") {
trigger.parameters = []
const endpoint = document.getElementById('endpoint')?.value
if(endpoint) {
trigger.parameters.push({
name: "endpoint",
value: endpoint
})
} else {
toast("Please enter your endpoint");
return;
}
}
};
const handleColoring = (actionId, status, label) => {
if (cy === undefined) {
return
@@ -8245,11 +8294,11 @@ const releaseToConnectLabel = "Release to Connect"
toast("Pipeline deleted!")
return
}
if (trigger.parameters){
trigger.parameters.push({
name: data.name,
value: data.command,
});
});}
if (data.type === "stop") trigger.status = "stopped";
else trigger.status = "running";
@@ -8257,7 +8306,6 @@ const releaseToConnectLabel = "Release to Connect"
setSelectedTrigger(trigger);
setWorkflow(workflow);
console.log("Should set the status to running and save");
saveWorkflow(workflow);
}
})
@@ -8332,6 +8380,32 @@ const releaseToConnectLabel = "Release to Connect"
});
};
const getSigmaInfo = () => {
const url = globalUrl + "/api/v1/files/detection/sigma_rules";
fetch(url, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
toast("Failed to get sigma rules");
} else {
setRules(responseJson.sigma_info);
}
})
)
.catch((error) => {
console.log("Error in getting sigma files: ", error);
toast("An error occurred while fetching sigma rules");
});
};
const parsedHeight = isMobile ? bodyHeight - appBarSize * 4 : bodyHeight - appBarSize - 50
const appViewStyle = {
marginLeft: 5,
@@ -15277,6 +15351,14 @@ const releaseToConnectLabel = "Release to Connect"
}
</div>
const defaultEnvironment = environments.find(
(env) => env.default && env.Name.toLowerCase() !== "cloud"
);
if (selectedTrigger.trigger_type === "PIPELINE" && selectedTrigger.environment === "onprem" && defaultEnvironment !== undefined) {
selectedTrigger.environment = defaultEnvironment.Name
setSelectedTrigger(selectedTrigger) }
const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "SCHEDULE" ? null :
<div style={appApiViewStyle}>
<h3 style={{ marginBottom: "5px" }}>
@@ -15375,14 +15457,32 @@ const releaseToConnectLabel = "Release to Connect"
<div
key="syslogListener"
onClick={() => {
// setSelectedOption("Syslog listener")
// setTenzirConfigModalOpen(true);
}}
if(selectedTrigger.status === "running"){
//toast("please stop the trigger to edit the configuration");
return;
} else {
setSelectedOption("Syslog listener");
const url = `${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}`
const command = `from tcp://192.168.1.100:5162 read syslog | import`
const pipelineConfig = {
command: command,
name: selectedTrigger.label,
type: "create",
environment: selectedTrigger.environment,
workflow_id: workflow.id,
trigger_id: selectedTrigger.id,
start_node: "",
url:url,
};
submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig);
}}}
style={{
border: "1px solid rgba(255,255,255,0.3)",
borderRadius: theme.palette.borderRadius,
padding: 10,
cursor: "not-allowed",
cursor: "pointer",
marginTop: 5,
display: "flex",
alignItems: "center",
@@ -15392,27 +15492,46 @@ const releaseToConnectLabel = "Release to Connect"
control={
<Radio
checked={selectedOption === "Syslog listener"}
onChange={() => setSelectedOption("Syslog listener")}
onChange={() => {
if (selectedTrigger.status !== "running"){
setSelectedOption("Syslog listener")}}
}
value={"Syslog listener"}
name="option"
disabled={true}
/>
}
label="Start Syslog listener"
label= {selectedOption === "Syslog listener" && selectedTrigger.status === "running" ? "listening at 192.168.1.100:5162" : "Start Syslog listener"}
/>
</div>
<div
key="sigmaRulesearch"
onClick={() => {
// setSelectedOption("Sigma Rulesearch")
// setTenzirConfigModalOpen(true);
}}
if(selectedTrigger.status === "running"){
// toast("please stop the trigger to edit the configuration");
return;
} else {
setSelectedOption("SigmaRule");
const url = `${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}`
const command = `export | sigma /var/lib/tenzir/sigma_rules | to ${url}`
const pipelineConfig = {
command: command,
name: selectedTrigger.label,
type: "create",
environment: selectedTrigger.environment,
workflow_id: workflow.id,
trigger_id: selectedTrigger.id,
start_node: "",
url:url,
};
submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig);
}}}
style={{
border: "1px solid rgba(255,255,255,0.3)",
borderRadius: theme.palette.borderRadius,
padding: 10,
cursor: "not-allowed",
cursor: "pointer",
marginTop: 5,
display: "flex",
alignItems: "center",
@@ -15421,11 +15540,12 @@ const releaseToConnectLabel = "Release to Connect"
<FormControlLabel
control={
<Radio
checked={selectedOption === "Sigma Rulesearch"}
onChange={() => setSelectedOption("Sigma Rulesearch")}
checked={selectedOption === "SigmaRule"}
onChange={() => {
if (selectedTrigger.status !== "running"){
setSelectedOption("SigmaRule")}}}
value={"Sigma Rulesearch"}
name="option"
disabled={true}
/>
}
label="Run Sigma Rulesearch"
@@ -15456,7 +15576,9 @@ const releaseToConnectLabel = "Release to Connect"
control={
<Radio
checked={selectedOption === "Kafka Queue"}
onChange={() => setSelectedOption("Kafka Queue")}
onChange={() => {
if (selectedTrigger.status !== "running"){
setSelectedOption("Kafka Queue")}}}
value={"Kafka Queue"}
name="option"
/>
@@ -15472,10 +15594,12 @@ const releaseToConnectLabel = "Release to Connect"
disabled={selectedTrigger.status === "running"}
onClick={() => {
if (selectedOption === "Kafka Queue"){
const url = `${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}`
const topic = (selectedTrigger?.parameters?.find(param => param.name === "topic")?.value) || ''
const bootstrapServers = (selectedTrigger?.parameters?.find(param => param.name === "bootstrap_servers")?.value) || ''
const groupId = (selectedTrigger?.parameters?.find(param => param.name === "group_id")?.value) || ''
// const autoOffsetReset = (selectedTrigger?.parameters?.find(param => param.name === "auto_offset_reset")?.value) || ''
const autoOffsetReset = (selectedTrigger?.parameters?.find(param => param.name === "auto_offset_reset")?.value) || ''
let command = "from kafka"
if(topic) {
@@ -15496,15 +15620,15 @@ const releaseToConnectLabel = "Release to Connect"
} else {
command = `${command},group.id=${selectedTrigger.id}`
}
// if(autoOffsetReset) {
// command = `${command},auto.offset.reset=${autoOffsetReset}`
// } else {
// command = `${command},auto.offset.reset=earliest`
if(autoOffsetReset) {
command = `${command},auto.offset.reset=${autoOffsetReset}`
} else {
command = `${command},auto.offset.reset=earliest`
// }
}
command = `${command},auto.offset.reset=earliest`
command = `${command},client.id=${selectedTrigger.id},enable.auto.commit=true,auto.commit.interval.ms=1`
command = `${command} read json | to ${globalUrl}/api/v1/pipelines/pipeline_${selectedTrigger.id}`
command = `${command} read json | to ${url}`
const pipelineConfig = {
command: command,
@@ -15514,9 +15638,11 @@ const releaseToConnectLabel = "Release to Connect"
workflow_id: workflow.id,
trigger_id: selectedTrigger.id,
start_node: "",
url: url,
};
submitPipeline(selectedTrigger, selectedTriggerIndex, pipelineConfig);
}}
}
}}
color="primary"
>
Start
@@ -21229,146 +21355,142 @@ const releaseToConnectLabel = "Release to Connect"
</Dialog>
) : null;
const tenzirConfigModal = tenzirConfigModalOpen ? (
<Dialog
PaperComponent={PaperComponent}
hideBackdrop={true}
disableEnforceFocus={true}
disableBackdropClick={true}
style={{ pointerEvents: "none" }}
open={tenzirConfigModalOpen}
PaperProps={{
style: {
pointerEvents: "auto",
color: "white",
minWidth: 600,
minHeight: 450,
maxHeight: 450,
padding: 15,
overflow: "hidden",
zIndex: 10012,
border: theme.palette.defaultBorder,
},
}}
>
<div
style={{
flex: 2,
padding: 0,
minHeight: isMobile ? "90%" : 700,
maxHeight: isMobile ? "90%" : 700,
overflowY: "auto",
overflowX: isMobile ? "auto" : "hidden",
}}
>
<DialogTitle id="tenzir-config-modal" style={{ cursor: "move" }}>
<div style={{ color: "white" }}>Configuration options for {selectedOption}</div>
</DialogTitle>
<DialogContent>
{selectedOption === "Kafka Queue" && (
<>
<b>Topic</b>
<TextField
id="topic"
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
InputProps={{
style: {},
}}
fullWidth
color="primary"
placeholder={"topic name"}
defaultValue={(selectedTrigger?.parameters?.find(param => param.name === "topic")?.value) || ''}
/>
<b>bootstrap.servers</b>
<TextField
id="bootstrap_servers"
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
InputProps={{
style: {},
}}
fullWidth
color="primary"
placeholder={"broker1.example.com:9092,192.168.1.100:9092"}
defaultValue={(selectedTrigger?.parameters?.find(param => param.name === "bootstrap_servers")?.value) || ''}
/>
<b>group.id</b>
<TextField
id="group_id"
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
InputProps={{
style: {},
}}
fullWidth
color="primary"
placeholder={"tenzir"}
defaultValue={(selectedTrigger?.parameters?.find(param => param.name === "group_id")?.value) || ''}
/>
{/* <b>auto.offest.reset</b>
<TextField
id="auto_offset_reset"
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
InputProps={{
style: {},
}}
fullWidth
color="primary"
placeholder={"earliest"}
defaultValue={(selectedTrigger?.parameters?.find(param => param.name === "auto_offset_reset")?.value) || ''}
/> */}
</>
)}
</DialogContent>
<DialogActions>
<Button
style={{ borderRadius: "0px" }}
onClick={() => {
setTenzirConfigModalOpen(false);
const TenzirConfigModal = () => {
if (!tenzirConfigModalOpen) return null;
return (
<Dialog
PaperComponent={PaperComponent}
hideBackdrop={true}
disableEnforceFocus={true}
disableBackdropClick={true}
style={{ pointerEvents: "none" }}
open={tenzirConfigModalOpen}
PaperProps={{
style: {
pointerEvents: "auto",
color: "white",
minWidth: 600,
minHeight: 550,
maxHeight: 550,
padding: 15,
overflow: "hidden",
zIndex: 10012,
border: theme.palette.defaultBorder,
},
}}
>
<DialogTitle id="tenzir-config-modal" style={{ cursor: "move" }}>
<div style={{ color: "white" }}>Configuration options for Kafka</div>
</DialogTitle>
<DialogContent>
{selectedOption === "Kafka Queue" ? (
<div>
<b>Topic</b>
<TextField
id="topic"
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
color="primary"
>
Cancel
</Button>
<Button
style={{ borderRadius: "0px" }}
onClick={() => {
handleKafkaSubmit(selectedTrigger);
InputProps={{
style: {},
}}
fullWidth
color="primary"
>
Submit
</Button>
</DialogActions>
</div>
placeholder={"topic name"}
defaultValue={
selectedTrigger?.parameters?.find(
(param) => param.name === "topic",
)?.value || ""
}
/>
<b>bootstrap.servers</b>
<TextField
id="bootstrap_servers"
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
InputProps={{
style: {},
}}
fullWidth
color="primary"
placeholder={"broker1.example.com:9092,192.168.1.100:9092"}
defaultValue={
selectedTrigger?.parameters?.find(
(param) => param.name === "bootstrap_servers",
)?.value || ""
}
/>
<b>group.id</b>
<TextField
id="group_id"
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
InputProps={{
style: {},
}}
fullWidth
color="primary"
placeholder={"tenzir"}
defaultValue={
selectedTrigger?.parameters?.find(
(param) => param.name === "group_id",
)?.value || ""
}
/>
<b>auto.offest.reset</b>
<TextField
id="auto_offset_reset"
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
InputProps={{
style: {},
}}
fullWidth
color="primary"
placeholder={"earliest"}
defaultValue={
selectedTrigger?.parameters?.find(
(param) => param.name === "auto_offset_reset",
)?.value || ""
}
/>
</div>
) : null}{" "}
<IconButton
style={{
zIndex: 5000,
position: "absolute",
top: 14,
right: 18,
color: "grey",
}}
</DialogContent>
<DialogActions>
<Button
style={{ borderRadius: "0px" }}
onClick={() => {
setTenzirConfigModalOpen(false);
}}
color="primary"
>
<CloseIcon />
</IconButton>
</Dialog>
) : null;
Cancel
</Button>
<Button
style={{ borderRadius: "0px" }}
onClick={() => {
handleSubmit(selectedTrigger);
}}
color="primary"
>
Submit
</Button>
</DialogActions>
</Dialog>
);
};
const SuggestionBoxUi = () => {
@@ -21947,7 +22069,7 @@ const releaseToConnectLabel = "Release to Connect"
{codePopoutModal}
{workflowRevisions}
{authenticationModal}
{tenzirConfigModal}
{<TenzirConfigModal/>}
{/*editWorkflowModal*/}
{authgroupModal}
{executionArgumentModal}
+198
View File
@@ -0,0 +1,198 @@
import React, { useState } from "react";
import {
Container,
Box,
TextField,
Switch,
Typography,
Button,
} from "@mui/material";
import { toast } from "react-toastify";
import RuleCard from "./RuleCard";
import CircularProgress from "@material-ui/core/CircularProgress";
const handleDirectoryChange = (folderDisabled, setFolderDisabled, globalUrl, isTenzirActive) => {
if (!isTenzirActive) {
toast("connect to siem first for global enable/disable to work");
return;
}
const action = folderDisabled ? "enable_folder" : "disable_folder";
const url = `${globalUrl}/api/v1/files/detection/${action}`;
fetch(url, {
method: "PUT",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === true) {
if (action === "enable_folder") setFolderDisabled(false);
else setFolderDisabled(true);
} else {
//toast(`failed to disable rule`);
}
})
)
.catch((error) => {
console.log(`Error in ${action} the rule: `, error);
toast(`An error occurred while ${action} the rule`);
});
};
const Detection = ({
globalUrl,
ruleInfo,
folderDisabled,
setFolderDisabled,
isTenzirActive,
}) => {
const [searchQuery, setSearchQuery] = useState("");
const [loading, setLoading] = useState(false);
const handleConnectClick = () => {
if (!isTenzirActive) {
setLoading(true);
const url = `${globalUrl}/api/v1/detection/siem/connect`;
fetch(url, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === true) {
setTimeout(() => {
setLoading(false);
window.location.reload();
}, 15000);
} else {
setLoading(false);
toast("Failed to connect to SIEM");
}
})
)
.catch((error) => {
setLoading(false);
console.log(`Error in connecting to SIEM: `, error);
toast("An error occurred while connecting to SIEM");
});
} else {
console.log("Already connected to SIEM");
}
};
const filteredRules = ruleInfo?.filter((rule) =>
rule.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
rule.description.toLowerCase().includes(searchQuery.toLowerCase())
);
return (
<Container sx={{ mt: 4 }}>
<Box sx={{ border: "1px solid #ccc", borderRadius: 2, p: 3 }}>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
mb: 2,
}}
>
<Typography variant="h6" component="div">
Sigma Detection Rules
</Typography>
<Button
variant="contained"
onClick={handleConnectClick}
disabled={loading} // Disable the button while loading
style={{ backgroundColor: isTenzirActive ? "green" : "red"}}
>
{loading ? <CircularProgress size={24} /> : isTenzirActive ? "Connected to siem" : "Connect to siem"}
</Button>
</Box>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
mb: 2,
}}
>
<Box
sx={{
display: "flex",
}}
>
<TextField
label="Search rules"
variant="outlined"
size="small"
sx={{ mr: 2 }}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
{/* <Button
color="primary"
variant="contained"
onClick={() => uploadRef.current.click()}
>
<PublishIcon /> Upload sigma file
</Button>
<input
hidden
type="file"
multiple
ref={uploadRef}
onChange={(event) => {
uploadFiles(event.target.files);
}}
/> */}
</Box>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Typography variant="body2" sx={{ mr: 1 }}>
Global disable/enable
</Typography>
<Switch
checked={!folderDisabled}
onChange={() =>
handleDirectoryChange(folderDisabled, setFolderDisabled, globalUrl, isTenzirActive)
}
disabled={!isTenzirActive}
/>
</Box>
</Box>
<Box
sx={{
height: "500px",
width: "100%",
overflowY: "auto",
border: "1px solid #ddd",
p: 1,
}}
>
{filteredRules?.length > 0 &&
filteredRules.map((card) => (
<RuleCard
key={card.file_id}
ruleName={card.title}
description={card.description}
file_id={card.file_id}
globalUrl={globalUrl}
folderDisabled={folderDisabled}
isTenzirActive={isTenzirActive}
{...card}
/>
))}
</Box>
</Box>
</Container>
);
};
export default Detection;
+162
View File
@@ -0,0 +1,162 @@
import React, { useState, useEffect } from "react";
import { Container, CircularProgress, Typography } from "@mui/material";
import { toast } from "react-toastify";
import Detection from "./Detection";
const DetectionDashBoard = (props) => {
const { globalUrl } = props;
const [ruleInfo, setRuleInfo] = useState(null);
const [, setSelectedRule] = useState(null);
const [, setFileData] = useState("");
const [isTenzirActive, setIsTenzirActive] = useState(false);
const [folderDisabled, setFolderDisabled] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [importAttempts, setImportAttempts] = useState(0);
const maxImportAttempts = 2;
useEffect(() => {
const fetchTimeout = setTimeout(() => {
fetchSigmaInfo();
}, 1000); // Delay by 1 second
return () => clearTimeout(fetchTimeout);
}, [globalUrl]);
useEffect(() => {
if (ruleInfo && ruleInfo.length === 0 && importAttempts < maxImportAttempts) {
importSigmaFromUrl();
}
}, [ruleInfo]);
const openEditBar = (rule) => {
setSelectedRule(rule);
fetchFileContent(rule.file_id);
};
const handleSave = (updatedContent) => {
toast("This will be saved");
};
const fetchFileContent = (file_id) => {
setFileData("");
fetch(`${globalUrl}/api/v1/files/${file_id}/content`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for file :O!");
return "";
}
return response.text();
})
.then((respdata) => {
if (respdata.length === 0) {
toast("Failed getting file. Is it deleted?");
return;
}
setFileData(respdata);
})
.catch((error) => {
toast(error.toString());
});
};
const fetchSigmaInfo = () => {
const url = `${globalUrl}/api/v1/files/detection/sigma_rules`;
setIsLoading(true);
fetch(url, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((responseJson) => {
if (responseJson["success"] === false) {
toast("Failed to get sigma rules");
} else {
setRuleInfo(responseJson.sigma_info || []);
setFolderDisabled(responseJson.folder_disabled);
setIsTenzirActive(responseJson.is_tenzir_active);
}
setIsLoading(false);
})
.catch((error) => {
setIsLoading(false);
console.log("Error in getting sigma files: ", error);
toast("An error occurred while fetching sigma rules");
setRuleInfo([]);
});
};
const importSigmaFromUrl = () => {
setIsLoading(true);
setImportAttempts((prevAttempts) => prevAttempts + 1);
const url = "https://github.com/satti-hari-krishna-reddy/shuffle_sigma";
const folder = "sigma";
const parsedData = {
url: url,
path: folder,
field_3: "main",
};
toast(`Getting files from url ${url}. This may take a while if the repository is large. Please wait...`);
fetch(`${globalUrl}/api/v1/files/download_remote_enhanced`, {
method: "POST",
mode: "cors",
headers: {
Accept: "application/json",
},
body: JSON.stringify(parsedData),
credentials: "include",
})
.then((response) => response.json())
.then((responseJson) => {
if (responseJson.success) {
toast("Successfully loaded files from " + url);
fetchSigmaInfo(); // Fetch again after successful import
} else {
toast(responseJson.reason ? `Failed loading: ${responseJson.reason}` : "Failed loading");
}
setIsLoading(false);
})
.catch((error) => {
toast(error.toString());
setIsLoading(false);
});
};
if (isLoading && (!ruleInfo || ruleInfo.length === 0)) {
return (
<Container style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100vh" }}>
<div>
<CircularProgress />
<Typography variant="h6" style={{ marginTop: 20 }}>Downloading rules, please wait...</Typography>
</div>
</Container>
);
}
return (
<Container style={{ display: "flex" }}>
<Detection
globalUrl={globalUrl}
ruleInfo={ruleInfo}
folderDisabled={folderDisabled}
setFolderDisabled={setFolderDisabled}
isTenzirActive={isTenzirActive}
/>
</Container>
);
};
export default DetectionDashBoard;
+43
View File
@@ -0,0 +1,43 @@
import React from 'react';
import { Box, Typography, Button, TextField } from '@mui/material';
const EditComponent = ({ ruleName, description, content, setContent, lastEdited, editedBy, onSave }) => {
const handleSave = () => {
onSave(content);
};
return (
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: 2, height: '100%', width: '100%', marginTop:'30px'}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="h6">{ruleName}</Typography>
</Box>
<Typography variant="body2" style={{ marginTop: '2%' }}>
{description}
</Typography>
<Typography variant="body2" sx={{ mt: 1 }}>
Last edited: {lastEdited}
</Typography>
<Typography variant="body2">
Edited By: {editedBy}
</Typography>
<Box sx={{ mt: 2 }}>
<TextField
multiline
rows={12}
value={content}
onChange={(e) => setContent(e.target.value)}
variant="outlined"
fullWidth
/>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 2 }}>
<Button variant="contained" color="primary" onClick={handleSave}>
Save
</Button>
</Box>
</Box>
);
};
export default EditComponent;
+168
View File
@@ -0,0 +1,168 @@
import React from "react";
import {
Card,
CardContent,
IconButton,
Typography,
Switch,
} from "@mui/material";
import EditIcon from "@mui/icons-material/Edit";
import { toast } from "react-toastify";
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, isTenzirActive, ...otherProps }) => {
const [openCodeEditor, setOpenCodeEditor] = React.useState(false);
const [fileData, setFileData] = React.useState("");
const [isEnabled, setIsEnabled] = React.useState(otherProps.is_enabled);
const isCloud = ["localhost:3002", "shuffler.io"].includes(window.location.host);
const handleSwitchChange = (event) => {
if (folderDisabled) {
toast("enable the directory to enable individual rules");
return;
}
if (!isTenzirActive) {
toast("connect to the siem to enable/disable the rule");
return;
}
const newIsEnabled = event.target.checked;
toggleRule(file_id, !newIsEnabled, globalUrl, () => {
setIsEnabled(newIsEnabled);
});
};
const UpdateText = (text) => {
fetch(`${globalUrl}/api/v1/files/${file_id}/edit`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: text,
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Can't update file");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success === true) {
toast("Successfully updated file");
}
})
.catch((error) => {
toast("Error updating file: " + error.toString());
});
};
return (
<Card variant="outlined" sx={{ mb: 2 }}>
<CardContent>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16,
}}
>
<Typography variant="h6">{ruleName}</Typography>
<div style={{ display: 'flex', alignItems: 'center' }}>
<IconButton onClick={() => openEditBar(file_id, setOpenCodeEditor, setFileData, globalUrl)}>
<EditIcon />
</IconButton>
<Switch
checked={isEnabled && !folderDisabled}
onChange={handleSwitchChange}
disabled={!isTenzirActive}
/>
</div>
</div>
<Typography variant="body2" style={{ marginTop: '2%' }}>
{description}
</Typography>
<ShuffleCodeEditor
isCloud={isCloud}
expansionModalOpen={openCodeEditor}
setExpansionModalOpen={setOpenCodeEditor}
setcodedata={setFileData}
codedata={fileData}
isFileEditor={true}
key={fileData} // https://reactjs.org/docs/reconciliation.html#recursing-on-children
runUpdateText={UpdateText}
/>
</CardContent>
</Card>
);
}
const toggleRule = (fileId, isCurrentlyEnabled, globalUrl, callback) => {
const action = isCurrentlyEnabled ? "disable" : "enable";
const url = `${globalUrl}/api/v1/files/detection/${fileId}/${action}_rule`;
fetch(url, {
method: "PUT",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
toast(`Failed to ${action} the rule`);
} else {
toast(`Rule ${action}d successfully`);
callback();
}
})
)
.catch((error) => {
console.log(`Error in ${action}ing the rule: `, error);
toast(`An error occurred while ${action}ing the rule`);
});
};
const openEditBar = (file_id, setOpenCodeEditor, setFileData, globalUrl) => {
getFileContent(file_id, setFileData, globalUrl)
setOpenCodeEditor(true);
};
const getFileContent = (file_id, setFileData, globalUrl) => {
setFileData("");
fetch(globalUrl + "/api/v1/files/" + file_id + "/content", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for file :O!");
return "";
}
return response.text();
})
.then((respdata) => {
if (respdata.length === 0) {
toast("Failed getting file. Is it deleted?");
return;
}
return respdata
})
.then((responseData) => {
setFileData(responseData);
})
.catch((error) => {
toast(error.toString());
});
};
export default RuleCard;