Merge pull request #1000 from guardicore/unsafe-options-confirmation

Unsafe options confirmation
This commit is contained in:
Mike Salvatore 2021-03-01 10:25:28 -05:00 committed by GitHub
commit cfaf4a15c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 170 additions and 21 deletions

View File

@ -11,6 +11,9 @@ from monkey_island.cc.services.config_schema.monkey import MONKEY
SCHEMA = {
"title": "Monkey",
"type": "object",
# Newly added definitions should also be added to
# monkey/monkey_island/cc/ui/src/components/utils/SafeOptionValidator.js so that
# users will not accidentally chose unsafe options
"definitions": {
"exploiter_classes": EXPLOITER_CLASSES,
"system_info_collector_classes": SYSTEM_INFO_COLLECTOR_CLASSES,

View File

@ -0,0 +1,36 @@
import React from 'react';
import {Modal, Button} from 'react-bootstrap';
function UnsafeOptionsConfirmationModal(props) {
return (
<Modal show={props.show}>
<Modal.Body>
<h2>
<div className='text-center'>Warning</div>
</h2>
<p className='text-center' style={{'fontSize': '1.2em', 'marginBottom': '2em'}}>
Some of the selected options could cause systems to become unstable or malfunction.
Are you sure you want to submit the selected settings?
</p>
<div className='text-center'>
<Button type='button'
className='btn btn-secondary'
size='lg'
style={{margin: '5px'}}
onClick={props.onCancelClick}>
Cancel
</Button>
<Button type='button'
className='btn btn-danger'
size='lg'
style={{margin: '5px'}}
onClick={props.onContinueClick}>
Submit
</Button>
</div>
</Modal.Body>
</Modal>
)
}
export default UnsafeOptionsConfirmationModal;

View File

@ -11,6 +11,8 @@ import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamati
import {formValidationFormats} from '../configuration-components/ValidationFormats';
import transformErrors from '../configuration-components/ValidationErrorMessages';
import InternalConfig from '../configuration-components/InternalConfig';
import UnsafeOptionsConfirmationModal from '../configuration-components/UnsafeOptionsConfirmationModal.js';
import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js';
const ATTACK_URL = '/api/attack';
const CONFIG_URL = '/api/configuration/island';
@ -28,13 +30,15 @@ class ConfigurePageComponent extends AuthComponent {
this.sectionsOrder = ['attack', 'basic', 'basic_network', 'monkey', 'internal'];
this.state = {
schema: {},
configuration: {},
attackConfig: {},
configuration: {},
importCandidateConfig: null,
lastAction: 'none',
schema: {},
sections: [],
selectedSection: 'attack',
showAttackAlert: false
showAttackAlert: false,
showUnsafeOptionsConfirmation: false
};
}
@ -74,6 +78,20 @@ class ConfigurePageComponent extends AuthComponent {
});
};
onUnsafeConfirmationCancelClick = () => {
this.setState({showUnsafeOptionsConfirmation: false});
}
onUnsafeConfirmationContinueClick = () => {
this.setState({showUnsafeOptionsConfirmation: false});
if (this.state.lastAction == 'submit_attempt') {
this.configSubmit();
} else if (this.state.lastAction == 'import_attempt') {
this.setConfigFromImportCandidate();
}
}
updateConfig = () => {
this.authFetch(CONFIG_URL)
.then(res => res.json())
@ -85,12 +103,16 @@ class ConfigurePageComponent extends AuthComponent {
onSubmit = () => {
if (this.state.selectedSection === 'attack') {
this.matrixSubmit()
this.matrixSubmit();
} else {
this.configSubmit()
this.attemptConfigSubmit();
}
};
canSafelySubmitConfig(config) {
return !isUnsafeOptionSelected(this.state.schema, config);
}
matrixSubmit = () => {
// Submit attack matrix
this.authFetch(ATTACK_URL,
@ -116,9 +138,19 @@ class ConfigurePageComponent extends AuthComponent {
});
};
configSubmit = () => {
// Submit monkey configuration
attemptConfigSubmit() {
this.updateConfigSection();
this.setState({lastAction: 'submit_attempt'}, () => {
if (this.canSafelySubmitConfig(this.state.configuration)) {
this.configSubmit();
} else {
this.setState({showUnsafeOptionsConfirmation: true});
}
}
);
}
configSubmit() {
this.sendConfig()
.then(res => res.json())
.then(res => {
@ -133,7 +165,7 @@ class ConfigurePageComponent extends AuthComponent {
console.log('Bad configuration: ' + error.toString());
this.setState({lastAction: 'invalid_configuration'});
});
};
}
// Alters attack configuration when user toggles technique
attackTechniqueChange = (technique, value, mapped = false) => {
@ -201,6 +233,16 @@ class ConfigurePageComponent extends AuthComponent {
</Modal>)
};
renderUnsafeOptionsConfirmationModal() {
return (
<UnsafeOptionsConfirmationModal
show={this.state.showUnsafeOptionsConfirmation}
onCancelClick={this.onUnsafeConfirmationCancelClick}
onContinueClick={this.onUnsafeConfirmationContinueClick}
/>
);
}
userChangedConfig() {
if (JSON.stringify(this.state.configuration) === JSON.stringify(this.initialConfig)) {
if (Object.keys(this.currentFormData).length === 0 ||
@ -276,18 +318,33 @@ class ConfigurePageComponent extends AuthComponent {
setConfigOnImport = (event) => {
try {
this.setState({
configuration: JSON.parse(event.target.result),
lastAction: 'import_success'
}, () => {
this.sendConfig();
this.setInitialConfig(JSON.parse(event.target.result))
});
this.currentFormData = {};
var newConfig = JSON.parse(event.target.result);
} catch (SyntaxError) {
this.setState({lastAction: 'import_failure'});
return;
}
};
this.setState({lastAction: 'import_attempt', importCandidateConfig: newConfig},
() => {
if (this.canSafelySubmitConfig(newConfig)) {
this.setConfigFromImportCandidate();
} else {
this.setState({showUnsafeOptionsConfirmation: true});
}
}
);
}
setConfigFromImportCandidate(){
this.setState({
configuration: this.state.importCandidateConfig,
lastAction: 'import_success'
}, () => {
this.sendConfig();
this.setInitialConfig(this.state.importCandidateConfig);
});
this.currentFormData = {};
}
exportConfig = () => {
this.updateConfigSection();
@ -410,6 +467,7 @@ class ConfigurePageComponent extends AuthComponent {
lg={{offset: 3, span: 8}} xl={{offset: 2, span: 8}}
className={'main'}>
{this.renderAttackAlertModal()}
{this.renderUnsafeOptionsConfirmationModal()}
<h1 className='page-title'>Monkey Configuration</h1>
{this.renderNav()}
{content}
@ -424,7 +482,7 @@ class ConfigurePageComponent extends AuthComponent {
<div className='text-center'>
<button onClick={() => document.getElementById('uploadInputInternal').click()}
className='btn btn-info btn-lg' style={{margin: '5px'}}>
Import Config
Import config
</button>
<input id='uploadInputInternal' type='file' accept='.conf' onChange={this.importConfig}
style={{display: 'none'}}/>

View File

@ -48,13 +48,14 @@ class AdvancedMultiSelect extends React.Component {
};
}
// Sort options alphabetically. "Unsafe" options float to the bottom"
// Sort options alphabetically. "Unsafe" options float to the top so that they
// do not get selected and hidden at the bottom of the list.
compareOptions = (a, b) => {
// Apparently, you can use additive operators with boolean types. Ultimately,
// the ToNumber() abstraction operation is called to convert the booleans to
// numbers: https://tc39.es/ecma262/#sec-tonumeric
if (this.isSafe(b.value) - this.isSafe(a.value) !== 0) {
return this.isSafe(b.value) - this.isSafe(a.value);
if (this.isSafe(a.value) - this.isSafe(b.value) !== 0) {
return this.isSafe(a.value) - this.isSafe(b.value);
}
return a.value.localeCompare(b.value);

View File

@ -0,0 +1,51 @@
function getPluginDescriptors(schema, config) {
return ([
{
name: 'Exploiters',
allPlugins: schema.definitions.exploiter_classes.anyOf,
selectedPlugins: config.basic.exploiters.exploiter_classes
},
{
name: 'Fingerprinters',
allPlugins: schema.definitions.finger_classes.anyOf,
selectedPlugins: config.internal.classes.finger_classes
},
{
name: 'PostBreachActions',
allPlugins: schema.definitions.post_breach_actions.anyOf,
selectedPlugins: config.monkey.post_breach.post_breach_actions
},
{
name: 'SystemInfoCollectors',
allPlugins: schema.definitions.system_info_collector_classes.anyOf,
selectedPlugins: config.monkey.system_info.system_info_collector_classes
}
]);
}
function isUnsafeOptionSelected(schema, config) {
let pluginDescriptors = getPluginDescriptors(schema, config);
for (let descriptor of pluginDescriptors) {
if (isUnsafePluginSelected(descriptor)) {
return true;
}
}
return false;
}
function isUnsafePluginSelected(pluginDescriptor) {
let pluginSafety = new Map();
pluginDescriptor.allPlugins.forEach(i => pluginSafety[i.enum[0]] = i.safe);
for (let selected of pluginDescriptor.selectedPlugins) {
if (!pluginSafety[selected]) {
return true;
}
}
return false;
}
export default isUnsafeOptionSelected;