forked from p15670423/monkey
UI: Remove ATT&CK Configuration
This commit is contained in:
parent
f3b7803955
commit
23d05c37ed
|
@ -1,123 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import Checkbox from '../ui-components/Checkbox'
|
|
||||||
import Tooltip from 'react-tooltip-lite'
|
|
||||||
import AuthComponent from '../AuthComponent';
|
|
||||||
import ReactTable from 'react-table';
|
|
||||||
import 'filepond/dist/filepond.min.css';
|
|
||||||
import '../../styles/components/Tooltip.scss';
|
|
||||||
import {Col} from 'react-bootstrap';
|
|
||||||
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { faCircle as faCircle } from '@fortawesome/free-solid-svg-icons/faCircle';
|
|
||||||
import { faCircle as faCircleThin } from '@fortawesome/free-regular-svg-icons/faCircle';
|
|
||||||
|
|
||||||
class ConfigMatrixComponent extends AuthComponent {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {lastAction: 'none'}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finds which attack type has most techniques and returns that number
|
|
||||||
static findMaxTechniques(data) {
|
|
||||||
let maxLen = 0;
|
|
||||||
data.forEach(function (techType) {
|
|
||||||
if (Object.keys(techType.properties).length > maxLen) {
|
|
||||||
maxLen = Object.keys(techType.properties).length
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return maxLen
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parses ATT&CK config schema into data suitable for react-table (ATT&CK matrix)
|
|
||||||
static parseTechniques(data, maxLen) {
|
|
||||||
let techniques = [];
|
|
||||||
// Create rows with attack techniques
|
|
||||||
for (let i = 0; i < maxLen; i++) {
|
|
||||||
let row = {};
|
|
||||||
data.forEach(function (techType) {
|
|
||||||
let rowColumn = {};
|
|
||||||
rowColumn.techName = techType.title;
|
|
||||||
|
|
||||||
if (i <= Object.keys(techType.properties).length) {
|
|
||||||
rowColumn.technique = Object.values(techType.properties)[i];
|
|
||||||
if (rowColumn.technique) {
|
|
||||||
rowColumn.technique.name = Object.keys(techType.properties)[i];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rowColumn.technique = null;
|
|
||||||
}
|
|
||||||
row[rowColumn.techName] = rowColumn;
|
|
||||||
});
|
|
||||||
techniques.push(row)
|
|
||||||
}
|
|
||||||
return techniques;
|
|
||||||
}
|
|
||||||
|
|
||||||
getColumns(matrixData) {
|
|
||||||
return Object.keys(matrixData[0]).map((key) => {
|
|
||||||
return {
|
|
||||||
Header: key,
|
|
||||||
id: key,
|
|
||||||
accessor: x => this.renderTechnique(x[key].technique),
|
|
||||||
style: {'whiteSpace': 'unset'}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTechnique(technique) {
|
|
||||||
if (technique == null) {
|
|
||||||
return (<div/>)
|
|
||||||
} else {
|
|
||||||
return (<Tooltip content={technique.description} direction="down">
|
|
||||||
<Checkbox checked={technique.value}
|
|
||||||
necessary={technique.necessary}
|
|
||||||
name={technique.name}
|
|
||||||
changeHandler={this.props.change}>
|
|
||||||
{technique.title}
|
|
||||||
</Checkbox>
|
|
||||||
</Tooltip>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getTableData = (config) => {
|
|
||||||
let configCopy = JSON.parse(JSON.stringify(config));
|
|
||||||
let maxTechniques = ConfigMatrixComponent.findMaxTechniques(Object.values(configCopy));
|
|
||||||
let matrixTableData = ConfigMatrixComponent.parseTechniques(Object.values(configCopy), maxTechniques);
|
|
||||||
let columns = this.getColumns(matrixTableData);
|
|
||||||
return {'columns': columns, 'matrixTableData': matrixTableData, 'maxTechniques': maxTechniques}
|
|
||||||
};
|
|
||||||
|
|
||||||
renderLegend = () => {
|
|
||||||
return (
|
|
||||||
<div id="header" className="row justify-content-between attack-legend">
|
|
||||||
<Col xs={4}>
|
|
||||||
<FontAwesomeIcon icon={faCircleThin} className="icon-unchecked"/>
|
|
||||||
<span> - Disabled</span>
|
|
||||||
</Col>
|
|
||||||
<Col xs={4}>
|
|
||||||
<FontAwesomeIcon icon={faCircle} className="icon-checked"/>
|
|
||||||
<span> - Enabled</span>
|
|
||||||
</Col>
|
|
||||||
<Col xs={4}>
|
|
||||||
<FontAwesomeIcon icon={faCircle} className="icon-mandatory"/>
|
|
||||||
<span> - Mandatory</span>
|
|
||||||
</Col>
|
|
||||||
</div>)
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let tableData = this.getTableData(this.props.configuration);
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{this.renderLegend()}
|
|
||||||
<div className={'attack-matrix'}>
|
|
||||||
<ReactTable columns={tableData['columns']}
|
|
||||||
data={tableData['matrixTableData']}
|
|
||||||
showPagination={false}
|
|
||||||
defaultPageSize={tableData['maxTechniques']}/>
|
|
||||||
</div>
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ConfigMatrixComponent;
|
|
|
@ -1,5 +1,4 @@
|
||||||
const CONFIGURATION_TABS = {
|
const CONFIGURATION_TABS = {
|
||||||
ATTACK: 'attack',
|
|
||||||
BASIC: 'basic',
|
BASIC: 'basic',
|
||||||
BASIC_NETWORK: 'basic_network',
|
BASIC_NETWORK: 'basic_network',
|
||||||
RANSOMWARE: 'ransomware',
|
RANSOMWARE: 'ransomware',
|
||||||
|
@ -8,7 +7,6 @@ const CONFIGURATION_TABS = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const advancedModeConfigTabs = [
|
const advancedModeConfigTabs = [
|
||||||
CONFIGURATION_TABS.ATTACK,
|
|
||||||
CONFIGURATION_TABS.BASIC,
|
CONFIGURATION_TABS.BASIC,
|
||||||
CONFIGURATION_TABS.BASIC_NETWORK,
|
CONFIGURATION_TABS.BASIC_NETWORK,
|
||||||
CONFIGURATION_TABS.RANSOMWARE,
|
CONFIGURATION_TABS.RANSOMWARE,
|
||||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
||||||
import Form from 'react-jsonschema-form-bs4';
|
import Form from 'react-jsonschema-form-bs4';
|
||||||
import {Button, Col, Modal, Nav} from 'react-bootstrap';
|
import {Button, Col, Modal, Nav} from 'react-bootstrap';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
import ConfigMatrixComponent from '../attack/ConfigMatrixComponent';
|
|
||||||
import UiSchema from '../configuration-components/UiSchema';
|
import UiSchema from '../configuration-components/UiSchema';
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||||
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
|
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
|
||||||
|
@ -20,7 +19,6 @@ import applyUiSchemaManipulators from '../configuration-components/UISchemaManip
|
||||||
import HtmlFieldDescription from '../configuration-components/HtmlFieldDescription.js';
|
import HtmlFieldDescription from '../configuration-components/HtmlFieldDescription.js';
|
||||||
import CONFIGURATION_TABS_PER_MODE from '../configuration-components/ConfigurationTabs.js';
|
import CONFIGURATION_TABS_PER_MODE from '../configuration-components/ConfigurationTabs.js';
|
||||||
|
|
||||||
const ATTACK_URL = '/api/attack';
|
|
||||||
const CONFIG_URL = '/api/configuration/island';
|
const CONFIG_URL = '/api/configuration/island';
|
||||||
export const API_PBA_LINUX = '/api/fileUpload/PBAlinux';
|
export const API_PBA_LINUX = '/api/fileUpload/PBAlinux';
|
||||||
export const API_PBA_WINDOWS = '/api/fileUpload/PBAwindows';
|
export const API_PBA_WINDOWS = '/api/fileUpload/PBAwindows';
|
||||||
|
@ -30,11 +28,9 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.initialConfig = {};
|
this.initialConfig = {};
|
||||||
this.initialAttackConfig = {};
|
|
||||||
this.currentSection = this.getSectionsOrder()[0];
|
this.currentSection = this.getSectionsOrder()[0];
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
attackConfig: {},
|
|
||||||
configuration: {},
|
configuration: {},
|
||||||
currentFormData: {},
|
currentFormData: {},
|
||||||
importCandidateConfig: null,
|
importCandidateConfig: null,
|
||||||
|
@ -42,7 +38,7 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
schema: {},
|
schema: {},
|
||||||
sections: [],
|
sections: [],
|
||||||
selectedSection: this.currentSection,
|
selectedSection: this.currentSection,
|
||||||
showAttackAlert: false,
|
showUnsubmittedConfigWarning: false,
|
||||||
showUnsafeOptionsConfirmation: false,
|
showUnsafeOptionsConfirmation: false,
|
||||||
showUnsafeAttackOptionsWarning: false,
|
showUnsafeAttackOptionsWarning: false,
|
||||||
showConfigExportModal: false,
|
showConfigExportModal: false,
|
||||||
|
@ -64,39 +60,26 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
|
|
||||||
setInitialConfig(config) {
|
setInitialConfig(config) {
|
||||||
// Sets a reference to know if config was changed
|
// Sets a reference to know if config was changed
|
||||||
config['attack'] = {}
|
|
||||||
this.initialConfig = JSON.parse(JSON.stringify(config));
|
this.initialConfig = JSON.parse(JSON.stringify(config));
|
||||||
}
|
}
|
||||||
|
|
||||||
setInitialAttackConfig(attackConfig) {
|
|
||||||
// Sets a reference to know if attack config was changed
|
|
||||||
this.initialAttackConfig = JSON.parse(JSON.stringify(attackConfig));
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount = () => {
|
componentDidMount = () => {
|
||||||
let urls = [CONFIG_URL, ATTACK_URL];
|
let urls = [CONFIG_URL];
|
||||||
// ??? Why fetch config here and not in `render()`?
|
// ??? Why fetch config here and not in `render()`?
|
||||||
Promise.all(urls.map(url => this.authFetch(url).then(res => res.json())))
|
Promise.all(urls.map(url => this.authFetch(url).then(res => res.json())))
|
||||||
.then(data => {
|
.then(data => {
|
||||||
let sections = [];
|
let sections = [];
|
||||||
let attackConfig = data[1];
|
|
||||||
let monkeyConfig = data[0];
|
let monkeyConfig = data[0];
|
||||||
this.setInitialConfig(monkeyConfig.configuration);
|
this.setInitialConfig(monkeyConfig.configuration);
|
||||||
this.setInitialAttackConfig(attackConfig.configuration);
|
|
||||||
for (let sectionKey of this.getSectionsOrder()) {
|
for (let sectionKey of this.getSectionsOrder()) {
|
||||||
if (sectionKey === 'attack') {
|
sections.push({
|
||||||
sections.push({key: sectionKey, title: 'ATT&CK'})
|
key: sectionKey,
|
||||||
} else {
|
title: monkeyConfig.schema.properties[sectionKey].title
|
||||||
sections.push({
|
});
|
||||||
key: sectionKey,
|
|
||||||
title: monkeyConfig.schema.properties[sectionKey].title
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
schema: monkeyConfig.schema,
|
schema: monkeyConfig.schema,
|
||||||
configuration: monkeyConfig.configuration,
|
configuration: monkeyConfig.configuration,
|
||||||
attackConfig: attackConfig.configuration,
|
|
||||||
sections: sections,
|
sections: sections,
|
||||||
currentFormData: monkeyConfig.configuration[this.state.selectedSection]
|
currentFormData: monkeyConfig.configuration[this.state.selectedSection]
|
||||||
})
|
})
|
||||||
|
@ -130,42 +113,13 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
onSubmit = () => {
|
onSubmit = () => {
|
||||||
if (this.state.selectedSection === 'attack') {
|
this.attemptConfigSubmit();
|
||||||
this.matrixSubmit();
|
|
||||||
} else {
|
|
||||||
this.attemptConfigSubmit();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
canSafelySubmitConfig(config) {
|
canSafelySubmitConfig(config) {
|
||||||
return !isUnsafeOptionSelected(this.state.schema, config);
|
return !isUnsafeOptionSelected(this.state.schema, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
matrixSubmit = () => {
|
|
||||||
// Submit attack matrix
|
|
||||||
this.authFetch(ATTACK_URL,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify(this.state.attackConfig)
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
if (!res.ok) {
|
|
||||||
throw Error()
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
this.setInitialAttackConfig(this.state.attackConfig);
|
|
||||||
})
|
|
||||||
.then(() => this.updateConfig(this.checkAndShowUnsafeAttackWarning))
|
|
||||||
.then(() => this.setState({lastAction: 'saved'}))
|
|
||||||
.catch(error => {
|
|
||||||
console.log('Bad configuration: ' + error.toString());
|
|
||||||
this.setState({lastAction: 'invalid_configuration'});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
checkAndShowUnsafeAttackWarning = () => {
|
checkAndShowUnsafeAttackWarning = () => {
|
||||||
if (isUnsafeOptionSelected(this.state.schema, this.state.configuration)) {
|
if (isUnsafeOptionSelected(this.state.schema, this.state.configuration)) {
|
||||||
this.setState({showUnsafeAttackOptionsWarning: true});
|
this.setState({showUnsafeAttackOptionsWarning: true});
|
||||||
|
@ -201,38 +155,8 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alters attack configuration when user toggles technique
|
|
||||||
attackTechniqueChange = (technique, value, mapped = false) => {
|
|
||||||
// Change value in attack configuration
|
|
||||||
// Go trough each column in matrix, searching for technique
|
|
||||||
Object.entries(this.state.attackConfig).forEach(techType => {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(techType[1].properties, technique)) {
|
|
||||||
let tempMatrix = this.state.attackConfig;
|
|
||||||
tempMatrix[techType[0]].properties[technique].value = value;
|
|
||||||
this.setState({attackConfig: tempMatrix});
|
|
||||||
|
|
||||||
// Toggle all mapped techniques
|
|
||||||
if (!mapped) {
|
|
||||||
// Loop trough each column and each row
|
|
||||||
Object.entries(this.state.attackConfig).forEach(otherType => {
|
|
||||||
Object.entries(otherType[1].properties).forEach(otherTech => {
|
|
||||||
// If this technique depends on a technique that was changed
|
|
||||||
if (Object.prototype.hasOwnProperty.call(otherTech[1], 'depends_on') &&
|
|
||||||
otherTech[1]['depends_on'].includes(technique)) {
|
|
||||||
this.attackTechniqueChange(otherTech[0], value, true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onChange = ({formData}) => {
|
onChange = ({formData}) => {
|
||||||
let configuration = this.state.configuration;
|
let configuration = this.state.configuration;
|
||||||
if (this.state.selectedSection === 'attack'){
|
|
||||||
formData = {};
|
|
||||||
}
|
|
||||||
configuration[this.state.selectedSection] = formData;
|
configuration[this.state.selectedSection] = formData;
|
||||||
this.setState({currentFormData: formData, configuration: configuration});
|
this.setState({currentFormData: formData, configuration: configuration});
|
||||||
};
|
};
|
||||||
|
@ -270,8 +194,8 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAttackAlertModal = () => {
|
renderAttackAlertModal = () => {
|
||||||
return (<Modal show={this.state.showAttackAlert} onHide={() => {
|
return (<Modal show={this.state.showUnsubmittedConfigWarning} onHide={() => {
|
||||||
this.setState({showAttackAlert: false})
|
this.setState({showUnsubmittedConfigWarning: false})
|
||||||
}}>
|
}}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<h2>
|
<h2>
|
||||||
|
@ -286,7 +210,7 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
size='lg'
|
size='lg'
|
||||||
style={{margin: '5px'}}
|
style={{margin: '5px'}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.setState({showAttackAlert: false})
|
this.setState({showUnsubmittedConfigWarning: false})
|
||||||
}}>
|
}}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -330,16 +254,13 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
userChangedMatrix() {
|
|
||||||
return (JSON.stringify(this.state.attackConfig) !== JSON.stringify(this.initialAttackConfig))
|
|
||||||
}
|
|
||||||
|
|
||||||
setSelectedSection = (key) => {
|
setSelectedSection = (key) => {
|
||||||
if ((key === 'attack' && this.userChangedConfig()) ||
|
|
||||||
(this.currentSection === 'attack' && this.userChangedMatrix())) {
|
// TODO: Fix https://github.com/guardicore/monkey/issues/1621
|
||||||
this.setState({showAttackAlert: true});
|
//if ( key === 'basic' & this.userChangedConfig()) {
|
||||||
return;
|
// this.setState({showUnsubmittedConfigWarning: true});
|
||||||
}
|
// return;
|
||||||
|
//}
|
||||||
|
|
||||||
this.updateConfigSection();
|
this.updateConfigSection();
|
||||||
this.currentSection = key;
|
this.currentSection = key;
|
||||||
|
@ -358,7 +279,6 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
res.configuration['attack'] = {}
|
|
||||||
this.setState({
|
this.setState({
|
||||||
lastAction: 'reset',
|
lastAction: 'reset',
|
||||||
schema: res.schema,
|
schema: res.schema,
|
||||||
|
@ -372,16 +292,6 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
this.removePBAfile(API_PBA_WINDOWS, this.setPbaFilenameWindows)
|
this.removePBAfile(API_PBA_WINDOWS, this.setPbaFilenameWindows)
|
||||||
this.removePBAfile(API_PBA_LINUX, this.setPbaFilenameLinux)
|
this.removePBAfile(API_PBA_LINUX, this.setPbaFilenameLinux)
|
||||||
});
|
});
|
||||||
this.authFetch(ATTACK_URL, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify('reset_attack_matrix')
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(res => {
|
|
||||||
this.setState({attackConfig: res.configuration});
|
|
||||||
this.setInitialAttackConfig(res.configuration);
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
removePBAfile(apiEndpoint, setFilenameFnc) {
|
removePBAfile(apiEndpoint, setFilenameFnc) {
|
||||||
|
@ -421,13 +331,6 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMatrix = () => {
|
|
||||||
return (<ConfigMatrixComponent configuration={this.state.attackConfig}
|
|
||||||
submit={this.componentDidMount}
|
|
||||||
reset={this.resetConfig}
|
|
||||||
change={this.attackTechniqueChange}/>)
|
|
||||||
};
|
|
||||||
|
|
||||||
renderConfigContent = (displayedSchema) => {
|
renderConfigContent = (displayedSchema) => {
|
||||||
let formProperties = {};
|
let formProperties = {};
|
||||||
formProperties['schema'] = displayedSchema
|
formProperties['schema'] = displayedSchema
|
||||||
|
@ -497,15 +400,13 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let displayedSchema = {};
|
let displayedSchema = {};
|
||||||
if (Object.prototype.hasOwnProperty.call(this.state.schema, 'properties') && this.state.selectedSection !== 'attack') {
|
if (Object.prototype.hasOwnProperty.call(this.state.schema, 'properties')) {
|
||||||
displayedSchema = this.state.schema['properties'][this.state.selectedSection];
|
displayedSchema = this.state.schema['properties'][this.state.selectedSection];
|
||||||
displayedSchema['definitions'] = this.state.schema['definitions'];
|
displayedSchema['definitions'] = this.state.schema['definitions'];
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = '';
|
let content = '';
|
||||||
if (this.state.selectedSection === 'attack' && Object.entries(this.state.attackConfig).length !== 0) {
|
if (Object.entries(this.state.configuration).length !== 0) {
|
||||||
content = this.renderMatrix()
|
|
||||||
} else if (this.state.selectedSection !== 'attack' && Object.entries(this.state.configuration).length !== 0) {
|
|
||||||
content = this.renderConfigContent(displayedSchema)
|
content = this.renderConfigContent(displayedSchema)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|
Loading…
Reference in New Issue