Merge pull request #1459 from satti-hari-krishna-reddy/new-sigma
sigma detection ui
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user