forked from p34709852/monkey
Merge pull request #2069 from guardicore/2003-retrieve-submit-config
2003 retrieve submit config
This commit is contained in:
commit
62f6a7a1a8
|
@ -24,7 +24,7 @@ class AgentConfiguration(AbstractResource):
|
||||||
@jwt_required
|
@jwt_required
|
||||||
def post(self):
|
def post(self):
|
||||||
try:
|
try:
|
||||||
configuration_object = AgentConfigurationObject.from_json(request.data)
|
configuration_object = AgentConfigurationObject.from_mapping(request.json)
|
||||||
self._agent_configuration_repository.store_configuration(configuration_object)
|
self._agent_configuration_repository.store_configuration(configuration_object)
|
||||||
return make_response({}, 200)
|
return make_response({}, 200)
|
||||||
except (InvalidConfigurationError, json.JSONDecodeError) as err:
|
except (InvalidConfigurationError, json.JSONDecodeError) as err:
|
||||||
|
|
|
@ -4,6 +4,7 @@ import React, {useState} from 'react';
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import '../../styles/components/configuration-components/ExportConfigModal.scss';
|
import '../../styles/components/configuration-components/ExportConfigModal.scss';
|
||||||
import {encryptText} from '../utils/PasswordBasedEncryptor';
|
import {encryptText} from '../utils/PasswordBasedEncryptor';
|
||||||
|
import {reformatConfig} from './ReformatHook';
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -21,7 +22,7 @@ const ConfigExportModal = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSubmit() {
|
function onSubmit() {
|
||||||
let config = props.configuration;
|
let config = reformatConfig(props.configuration, true);
|
||||||
let config_export = {'metadata': {}, 'contents': null};
|
let config_export = {'metadata': {}, 'contents': null};
|
||||||
|
|
||||||
if (radioValue === 'password') {
|
if (radioValue === 'password') {
|
||||||
|
|
|
@ -20,8 +20,7 @@ type Props = {
|
||||||
|
|
||||||
|
|
||||||
const ConfigImportModal = (props: Props) => {
|
const ConfigImportModal = (props: Props) => {
|
||||||
// TODO: change this endpoint to the new configuration import endpoint
|
const configImportEndpoint = '/api/agent-configuration';
|
||||||
const configImportEndpoint = '/api/configuration/import';
|
|
||||||
|
|
||||||
const [uploadStatus, setUploadStatus] = useState(UploadStatuses.clean);
|
const [uploadStatus, setUploadStatus] = useState(UploadStatuses.clean);
|
||||||
const [configContents, setConfigContents] = useState(null);
|
const [configContents, setConfigContents] = useState(null);
|
||||||
|
@ -71,18 +70,15 @@ const ConfigImportModal = (props: Props) => {
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(configContents)
|
||||||
config: configContents,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
).then(res => res.json())
|
).then(res => {
|
||||||
.then(res => {
|
if (res.ok) {
|
||||||
if (res['import_status'] === 'invalid_configuration') {
|
|
||||||
setUploadStatus(UploadStatuses.error);
|
|
||||||
setErrorMessage(res['message']);
|
|
||||||
} else if (res['import_status'] === 'imported') {
|
|
||||||
resetState();
|
resetState();
|
||||||
props.onClose(true);
|
props.onClose(true);
|
||||||
|
} else {
|
||||||
|
setUploadStatus(UploadStatuses.error);
|
||||||
|
setErrorMessage("Configuration file is corrupt or in an outdated format.");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
export function reformatConfig(config, reverse = false) {
|
||||||
|
if (reverse) {
|
||||||
|
config['payloads'] = [{'name': 'ransomware', 'options': config['payloads']}]
|
||||||
|
} else {
|
||||||
|
config['payloads'] = config['payloads'][0]['options'];
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
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 {Col, Nav} from 'react-bootstrap';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
import UiSchema from '../configuration-components/UiSchema';
|
import UiSchema from '../configuration-components/UiSchema';
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||||
|
@ -10,7 +10,6 @@ import {formValidationFormats} from '../configuration-components/ValidationForma
|
||||||
import transformErrors from '../configuration-components/ValidationErrorMessages';
|
import transformErrors from '../configuration-components/ValidationErrorMessages';
|
||||||
import UnsafeConfigOptionsConfirmationModal
|
import UnsafeConfigOptionsConfirmationModal
|
||||||
from '../configuration-components/UnsafeConfigOptionsConfirmationModal.js';
|
from '../configuration-components/UnsafeConfigOptionsConfirmationModal.js';
|
||||||
import UnsafeOptionsWarningModal from '../configuration-components/UnsafeOptionsWarningModal.js';
|
|
||||||
import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js';
|
import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js';
|
||||||
import ConfigExportModal from '../configuration-components/ExportConfigModal';
|
import ConfigExportModal from '../configuration-components/ExportConfigModal';
|
||||||
import ConfigImportModal from '../configuration-components/ImportConfigModal';
|
import ConfigImportModal from '../configuration-components/ImportConfigModal';
|
||||||
|
@ -18,8 +17,10 @@ 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';
|
||||||
import {SCHEMA} from '../../services/configuration/config_schema.js';
|
import {SCHEMA} from '../../services/configuration/config_schema.js';
|
||||||
|
import {reformatConfig} from '../configuration-components/ReformatHook';
|
||||||
|
|
||||||
const CONFIG_URL = '/api/configuration/island';
|
const CONFIG_URL = '/api/agent-configuration';
|
||||||
|
const RESET_URL = '/api/reset-agent-configuration';
|
||||||
export const API_PBA_LINUX = '/api/file-upload/PBAlinux';
|
export const API_PBA_LINUX = '/api/file-upload/PBAlinux';
|
||||||
export const API_PBA_WINDOWS = '/api/file-upload/PBAwindows';
|
export const API_PBA_WINDOWS = '/api/file-upload/PBAwindows';
|
||||||
|
|
||||||
|
@ -42,9 +43,7 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
schema: {},
|
schema: {},
|
||||||
sections: [],
|
sections: [],
|
||||||
selectedSection: this.currentSection,
|
selectedSection: this.currentSection,
|
||||||
showUnsubmittedConfigWarning: false,
|
|
||||||
showUnsafeOptionsConfirmation: false,
|
showUnsafeOptionsConfirmation: false,
|
||||||
showUnsafeAttackOptionsWarning: false,
|
|
||||||
showConfigExportModal: false,
|
showConfigExportModal: false,
|
||||||
showConfigImportModal: false
|
showConfigImportModal: false
|
||||||
};
|
};
|
||||||
|
@ -57,8 +56,13 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetLastAction = () => {
|
||||||
|
this.setState({lastAction: 'none'});
|
||||||
|
}
|
||||||
|
|
||||||
getSectionsOrder() {
|
getSectionsOrder() {
|
||||||
let islandMode = this.props.islandMode !== 'unset' ? this.props.islandMode : 'advanced'
|
let islandModeSet = (this.props.islandMode !== 'unset' && this.props.islandMode !== undefined)
|
||||||
|
let islandMode = islandModeSet ? this.props.islandMode : 'advanced'
|
||||||
return CONFIGURATION_TABS_PER_MODE[islandMode];
|
return CONFIGURATION_TABS_PER_MODE[islandMode];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,14 +72,10 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount = () => {
|
componentDidMount = () => {
|
||||||
let urls = ['/api/agent-configuration'];
|
this.authFetch(CONFIG_URL).then(res => res.json())
|
||||||
// ??? Why fetch config here and not in `render()`?
|
.then(monkeyConfig => {
|
||||||
Promise.all(urls.map(url => this.authFetch(url).then(res => res.json())))
|
|
||||||
.then(data => {
|
|
||||||
let sections = [];
|
let sections = [];
|
||||||
let monkeyConfig = data[0];
|
monkeyConfig = reformatConfig(monkeyConfig);
|
||||||
// TODO: Fix when we add plugins
|
|
||||||
monkeyConfig['payloads'] = monkeyConfig['payloads'][0]['options'];
|
|
||||||
|
|
||||||
this.setInitialConfig(monkeyConfig);
|
this.setInitialConfig(monkeyConfig);
|
||||||
for (let sectionKey of this.getSectionsOrder()) {
|
for (let sectionKey of this.getSectionsOrder()) {
|
||||||
|
@ -108,17 +108,16 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onUnsafeAttackContinueClick = () => {
|
updateConfig = () => {
|
||||||
this.setState({showUnsafeAttackOptionsWarning: false});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateConfig = (callback = null) => {
|
|
||||||
this.authFetch(CONFIG_URL)
|
this.authFetch(CONFIG_URL)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
this.setInitialConfig(data.configuration);
|
data = reformatConfig(data);
|
||||||
this.setState({configuration: data.configuration,
|
this.setInitialConfig(data);
|
||||||
currentFormData: data.configuration[this.state.selectedSection]}, callback);
|
this.setState({
|
||||||
|
configuration: data,
|
||||||
|
currentFormData: data[this.state.selectedSection]
|
||||||
|
});
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -134,7 +133,7 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
await this.updateConfigSection();
|
await this.updateConfigSection();
|
||||||
if (this.canSafelySubmitConfig(this.state.configuration)) {
|
if (this.canSafelySubmitConfig(this.state.configuration)) {
|
||||||
this.configSubmit();
|
this.configSubmit();
|
||||||
if(this.state.lastAction === configExportAction){
|
if (this.state.lastAction === configExportAction) {
|
||||||
this.setState({showConfigExportModal: true})
|
this.setState({showConfigExportModal: true})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -145,18 +144,16 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
configSubmit() {
|
configSubmit() {
|
||||||
return this.sendConfig()
|
return this.sendConfig()
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
lastAction: configSaveAction,
|
lastAction: configSaveAction
|
||||||
schema: res.schema,
|
|
||||||
configuration: res.configuration
|
|
||||||
});
|
});
|
||||||
this.setInitialConfig(res.configuration);
|
this.setInitialConfig(this.state.configuration);
|
||||||
this.props.onStatusChange();
|
this.props.onStatusChange();
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.log('Bad configuration: ' + error.toString());
|
console.log('Bad configuration: ' + error.toString());
|
||||||
this.setState({lastAction: 'invalid_configuration'});
|
this.setState({lastAction: 'invalid_configuration'});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = ({formData}) => {
|
onChange = ({formData}) => {
|
||||||
|
@ -189,42 +186,18 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose = (importSuccessful) => {
|
onClose = (importSuccessful) => {
|
||||||
if(importSuccessful === true){
|
if (importSuccessful === true) {
|
||||||
this.updateConfig();
|
this.updateConfig();
|
||||||
this.setState({lastAction: 'import_success',
|
this.setState({
|
||||||
showConfigImportModal: false});
|
lastAction: 'import_success',
|
||||||
|
showConfigImportModal: false
|
||||||
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({showConfigImportModal: false});
|
this.setState({showConfigImportModal: false});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAttackAlertModal = () => {
|
|
||||||
return (<Modal show={this.state.showUnsubmittedConfigWarning} onHide={() => {
|
|
||||||
this.setState({showUnsubmittedConfigWarning: false})
|
|
||||||
}}>
|
|
||||||
<Modal.Body>
|
|
||||||
<h2>
|
|
||||||
<div className='text-center'>Warning</div>
|
|
||||||
</h2>
|
|
||||||
<p className='text-center' style={{'fontSize': '1.2em', 'marginBottom': '2em'}}>
|
|
||||||
You have unsubmitted changes. Submit them before proceeding.
|
|
||||||
</p>
|
|
||||||
<div className='text-center'>
|
|
||||||
<Button type='button'
|
|
||||||
className='btn btn-success'
|
|
||||||
size='lg'
|
|
||||||
style={{margin: '5px'}}
|
|
||||||
onClick={() => {
|
|
||||||
this.setState({showUnsubmittedConfigWarning: false})
|
|
||||||
}}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal.Body>
|
|
||||||
</Modal>)
|
|
||||||
};
|
|
||||||
|
|
||||||
renderUnsafeOptionsConfirmationModal() {
|
renderUnsafeOptionsConfirmationModal() {
|
||||||
return (
|
return (
|
||||||
<UnsafeConfigOptionsConfirmationModal
|
<UnsafeConfigOptionsConfirmationModal
|
||||||
|
@ -235,39 +208,8 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderUnsafeAttackOptionsWarningModal() {
|
|
||||||
return (
|
|
||||||
<UnsafeOptionsWarningModal
|
|
||||||
show={this.state.showUnsafeAttackOptionsWarning}
|
|
||||||
onContinueClick={this.onUnsafeAttackContinueClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
userChangedConfig() {
|
|
||||||
try {
|
|
||||||
if (JSON.stringify(this.state.configuration) === JSON.stringify(this.initialConfig)) {
|
|
||||||
if (Object.keys(this.state.currentFormData).length === 0 ||
|
|
||||||
JSON.stringify(this.initialConfig[this.currentSection]) === JSON.stringify(this.state.currentFormData)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (TypeError) {
|
|
||||||
if (JSON.stringify(this.initialConfig[this.currentSection]) === JSON.stringify(this.state.currentFormData)){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSelectedSection = (key) => {
|
setSelectedSection = (key) => {
|
||||||
|
this.resetLastAction();
|
||||||
// TODO: Fix https://github.com/guardicore/monkey/issues/1621
|
|
||||||
//if ( key === 'basic' & this.userChangedConfig()) {
|
|
||||||
// this.setState({showUnsubmittedConfigWarning: true});
|
|
||||||
// return;
|
|
||||||
//}
|
|
||||||
|
|
||||||
this.updateConfigSection();
|
this.updateConfigSection();
|
||||||
this.currentSection = key;
|
this.currentSection = key;
|
||||||
let selectedSectionData = this.state.configuration[key];
|
let selectedSectionData = this.state.configuration[key];
|
||||||
|
@ -279,21 +221,16 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
resetConfig = () => {
|
resetConfig = () => {
|
||||||
this.authFetch(CONFIG_URL,
|
this.authFetch(RESET_URL,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST'
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify({'reset': true})
|
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
lastAction: 'reset',
|
lastAction: 'reset'
|
||||||
schema: res.schema,
|
|
||||||
configuration: res.configuration,
|
|
||||||
currentFormData: res.configuration[this.state.selectedSection]
|
|
||||||
});
|
});
|
||||||
this.setInitialConfig(res.configuration);
|
this.updateConfig();
|
||||||
this.props.onStatusChange();
|
this.props.onStatusChange();
|
||||||
}
|
}
|
||||||
).then(() => {
|
).then(() => {
|
||||||
|
@ -321,12 +258,15 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
sendConfig() {
|
sendConfig() {
|
||||||
|
let config = JSON.parse(JSON.stringify(this.state.configuration))
|
||||||
|
config = reformatConfig(config, true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this.authFetch('/api/configuration/island',
|
this.authFetch(CONFIG_URL,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify(this.state.configuration)
|
body: JSON.stringify(config)
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
@ -352,14 +292,15 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
formProperties['fields'] = {DescriptionField: HtmlFieldDescription};
|
formProperties['fields'] = {DescriptionField: HtmlFieldDescription};
|
||||||
formProperties['formData'] = this.state.currentFormData;
|
formProperties['formData'] = this.state.currentFormData;
|
||||||
formProperties['onChange'] = this.onChange;
|
formProperties['onChange'] = this.onChange;
|
||||||
|
formProperties['onFocus'] = this.resetLastAction;
|
||||||
formProperties['customFormats'] = formValidationFormats;
|
formProperties['customFormats'] = formValidationFormats;
|
||||||
formProperties['transformErrors'] = transformErrors;
|
formProperties['transformErrors'] = transformErrors;
|
||||||
formProperties['className'] = 'config-form';
|
formProperties['className'] = 'config-form';
|
||||||
formProperties['liveValidate'] = true;
|
formProperties['liveValidate'] = true;
|
||||||
|
|
||||||
applyUiSchemaManipulators(this.state.selectedSection,
|
applyUiSchemaManipulators(this.state.selectedSection,
|
||||||
formProperties['formData'],
|
formProperties['formData'],
|
||||||
formProperties['uiSchema']);
|
formProperties['uiSchema']);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -419,9 +360,7 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
className={'main'}>
|
className={'main'}>
|
||||||
{this.renderConfigExportModal()}
|
{this.renderConfigExportModal()}
|
||||||
{this.renderConfigImportModal()}
|
{this.renderConfigImportModal()}
|
||||||
{this.renderAttackAlertModal()}
|
|
||||||
{this.renderUnsafeOptionsConfirmationModal()}
|
{this.renderUnsafeOptionsConfirmationModal()}
|
||||||
{this.renderUnsafeAttackOptionsWarningModal()}
|
|
||||||
<h1 className='page-title'>Monkey Configuration</h1>
|
<h1 className='page-title'>Monkey Configuration</h1>
|
||||||
{this.renderNav()}
|
{this.renderNav()}
|
||||||
{content}
|
{content}
|
||||||
|
|
|
@ -182,7 +182,7 @@ class AdvancedMultiSelect extends React.Component {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'advanced-multi-select'}>
|
<div className={'advanced-multi-select'} onFocus={this.props.onFocus}>
|
||||||
<AdvancedMultiSelectHeader title={schema.title}
|
<AdvancedMultiSelectHeader title={schema.title}
|
||||||
onCheckboxClick={this.onMasterCheckboxClick}
|
onCheckboxClick={this.onMasterCheckboxClick}
|
||||||
checkboxState={this.getMasterCheckboxState(
|
checkboxState={this.getMasterCheckboxState(
|
||||||
|
|
Loading…
Reference in New Issue