Merge pull request #2069 from guardicore/2003-retrieve-submit-config

2003 retrieve submit config
This commit is contained in:
VakarisZ 2022-07-08 15:10:38 +03:00 committed by GitHub
commit 62f6a7a1a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 65 additions and 121 deletions

View File

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

View File

@ -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') {

View File

@ -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.");
} }
}) })
} }

View File

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

View File

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

View File

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