UI: Remove ATT&CK Configuration

This commit is contained in:
Ilija Lazoroski 2021-11-17 17:13:55 +01:00
parent f3b7803955
commit 23d05c37ed
3 changed files with 18 additions and 242 deletions

View File

@ -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;

View File

@ -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,

View File

@ -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 (