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 = {
|
||||
ATTACK: 'attack',
|
||||
BASIC: 'basic',
|
||||
BASIC_NETWORK: 'basic_network',
|
||||
RANSOMWARE: 'ransomware',
|
||||
|
@ -8,7 +7,6 @@ const CONFIGURATION_TABS = {
|
|||
};
|
||||
|
||||
const advancedModeConfigTabs = [
|
||||
CONFIGURATION_TABS.ATTACK,
|
||||
CONFIGURATION_TABS.BASIC,
|
||||
CONFIGURATION_TABS.BASIC_NETWORK,
|
||||
CONFIGURATION_TABS.RANSOMWARE,
|
||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||
import Form from 'react-jsonschema-form-bs4';
|
||||
import {Button, Col, Modal, Nav} from 'react-bootstrap';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import ConfigMatrixComponent from '../attack/ConfigMatrixComponent';
|
||||
import UiSchema from '../configuration-components/UiSchema';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
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 CONFIGURATION_TABS_PER_MODE from '../configuration-components/ConfigurationTabs.js';
|
||||
|
||||
const ATTACK_URL = '/api/attack';
|
||||
const CONFIG_URL = '/api/configuration/island';
|
||||
export const API_PBA_LINUX = '/api/fileUpload/PBAlinux';
|
||||
export const API_PBA_WINDOWS = '/api/fileUpload/PBAwindows';
|
||||
|
@ -30,11 +28,9 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.initialConfig = {};
|
||||
this.initialAttackConfig = {};
|
||||
this.currentSection = this.getSectionsOrder()[0];
|
||||
|
||||
this.state = {
|
||||
attackConfig: {},
|
||||
configuration: {},
|
||||
currentFormData: {},
|
||||
importCandidateConfig: null,
|
||||
|
@ -42,7 +38,7 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
schema: {},
|
||||
sections: [],
|
||||
selectedSection: this.currentSection,
|
||||
showAttackAlert: false,
|
||||
showUnsubmittedConfigWarning: false,
|
||||
showUnsafeOptionsConfirmation: false,
|
||||
showUnsafeAttackOptionsWarning: false,
|
||||
showConfigExportModal: false,
|
||||
|
@ -64,39 +60,26 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
|
||||
setInitialConfig(config) {
|
||||
// Sets a reference to know if config was changed
|
||||
config['attack'] = {}
|
||||
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 = () => {
|
||||
let urls = [CONFIG_URL, ATTACK_URL];
|
||||
let urls = [CONFIG_URL];
|
||||
// ??? Why fetch config here and not in `render()`?
|
||||
Promise.all(urls.map(url => this.authFetch(url).then(res => res.json())))
|
||||
.then(data => {
|
||||
let sections = [];
|
||||
let attackConfig = data[1];
|
||||
let monkeyConfig = data[0];
|
||||
this.setInitialConfig(monkeyConfig.configuration);
|
||||
this.setInitialAttackConfig(attackConfig.configuration);
|
||||
for (let sectionKey of this.getSectionsOrder()) {
|
||||
if (sectionKey === 'attack') {
|
||||
sections.push({key: sectionKey, title: 'ATT&CK'})
|
||||
} else {
|
||||
sections.push({
|
||||
key: sectionKey,
|
||||
title: monkeyConfig.schema.properties[sectionKey].title
|
||||
});
|
||||
}
|
||||
sections.push({
|
||||
key: sectionKey,
|
||||
title: monkeyConfig.schema.properties[sectionKey].title
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
schema: monkeyConfig.schema,
|
||||
configuration: monkeyConfig.configuration,
|
||||
attackConfig: attackConfig.configuration,
|
||||
sections: sections,
|
||||
currentFormData: monkeyConfig.configuration[this.state.selectedSection]
|
||||
})
|
||||
|
@ -130,42 +113,13 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
};
|
||||
|
||||
onSubmit = () => {
|
||||
if (this.state.selectedSection === 'attack') {
|
||||
this.matrixSubmit();
|
||||
} else {
|
||||
this.attemptConfigSubmit();
|
||||
}
|
||||
this.attemptConfigSubmit();
|
||||
};
|
||||
|
||||
canSafelySubmitConfig(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 = () => {
|
||||
if (isUnsafeOptionSelected(this.state.schema, this.state.configuration)) {
|
||||
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}) => {
|
||||
let configuration = this.state.configuration;
|
||||
if (this.state.selectedSection === 'attack'){
|
||||
formData = {};
|
||||
}
|
||||
configuration[this.state.selectedSection] = formData;
|
||||
this.setState({currentFormData: formData, configuration: configuration});
|
||||
};
|
||||
|
@ -270,8 +194,8 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
}
|
||||
|
||||
renderAttackAlertModal = () => {
|
||||
return (<Modal show={this.state.showAttackAlert} onHide={() => {
|
||||
this.setState({showAttackAlert: false})
|
||||
return (<Modal show={this.state.showUnsubmittedConfigWarning} onHide={() => {
|
||||
this.setState({showUnsubmittedConfigWarning: false})
|
||||
}}>
|
||||
<Modal.Body>
|
||||
<h2>
|
||||
|
@ -286,7 +210,7 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
size='lg'
|
||||
style={{margin: '5px'}}
|
||||
onClick={() => {
|
||||
this.setState({showAttackAlert: false})
|
||||
this.setState({showUnsubmittedConfigWarning: false})
|
||||
}}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
@ -330,16 +254,13 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
return true;
|
||||
}
|
||||
|
||||
userChangedMatrix() {
|
||||
return (JSON.stringify(this.state.attackConfig) !== JSON.stringify(this.initialAttackConfig))
|
||||
}
|
||||
|
||||
setSelectedSection = (key) => {
|
||||
if ((key === 'attack' && this.userChangedConfig()) ||
|
||||
(this.currentSection === 'attack' && this.userChangedMatrix())) {
|
||||
this.setState({showAttackAlert: true});
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Fix https://github.com/guardicore/monkey/issues/1621
|
||||
//if ( key === 'basic' & this.userChangedConfig()) {
|
||||
// this.setState({showUnsubmittedConfigWarning: true});
|
||||
// return;
|
||||
//}
|
||||
|
||||
this.updateConfigSection();
|
||||
this.currentSection = key;
|
||||
|
@ -358,7 +279,6 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
res.configuration['attack'] = {}
|
||||
this.setState({
|
||||
lastAction: 'reset',
|
||||
schema: res.schema,
|
||||
|
@ -372,16 +292,6 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
this.removePBAfile(API_PBA_WINDOWS, this.setPbaFilenameWindows)
|
||||
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) {
|
||||
|
@ -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) => {
|
||||
let formProperties = {};
|
||||
formProperties['schema'] = displayedSchema
|
||||
|
@ -497,15 +400,13 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
|
||||
render() {
|
||||
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['definitions'] = this.state.schema['definitions'];
|
||||
}
|
||||
|
||||
let content = '';
|
||||
if (this.state.selectedSection === 'attack' && Object.entries(this.state.attackConfig).length !== 0) {
|
||||
content = this.renderMatrix()
|
||||
} else if (this.state.selectedSection !== 'attack' && Object.entries(this.state.configuration).length !== 0) {
|
||||
if (Object.entries(this.state.configuration).length !== 0) {
|
||||
content = this.renderConfigContent(displayedSchema)
|
||||
}
|
||||
return (
|
||||
|
|
Loading…
Reference in New Issue