Implemented the skeleton of import config modal

This commit is contained in:
VakarisZ 2021-05-28 18:06:37 +03:00
parent 6e2da3c4a5
commit 34024794c8
6 changed files with 188 additions and 42 deletions

View File

@ -52,3 +52,15 @@ class FindingWithoutDetailsError(Exception):
class DomainControllerNameFetchError(FailedExploitationError): class DomainControllerNameFetchError(FailedExploitationError):
""" Raise on failed attempt to extract domain controller's name """ """ Raise on failed attempt to extract domain controller's name """
class InvalidCredentialsError(Exception):
""" Raise when credentials supplied are invalid"""
class NoCredentialsError(Exception):
""" Raise when no credentials have been supplied"""
class InvalidConfigurationError(Exception):
""" Raise when configuration is invalid """

View File

@ -42,6 +42,7 @@ from monkey_island.cc.resources.security_report import SecurityReport
from monkey_island.cc.resources.T1216_pba_file_download import T1216PBAFileDownload from monkey_island.cc.resources.T1216_pba_file_download import T1216PBAFileDownload
from monkey_island.cc.resources.telemetry import Telemetry from monkey_island.cc.resources.telemetry import Telemetry
from monkey_island.cc.resources.telemetry_feed import TelemetryFeed from monkey_island.cc.resources.telemetry_feed import TelemetryFeed
from monkey_island.cc.resources.temp_configuration import TempConfiguration
from monkey_island.cc.resources.version_update import VersionUpdate from monkey_island.cc.resources.version_update import VersionUpdate
from monkey_island.cc.resources.zero_trust.finding_event import ZeroTrustFindingEvent from monkey_island.cc.resources.zero_trust.finding_event import ZeroTrustFindingEvent
from monkey_island.cc.resources.zero_trust.scoutsuite_auth.aws_keys import AWSKeys from monkey_island.cc.resources.zero_trust.scoutsuite_auth.aws_keys import AWSKeys
@ -118,6 +119,9 @@ def init_app_url_rules(app):
def init_api_resources(api): def init_api_resources(api):
# TODO hook up to a proper endpoint
api.add_resource(TempConfiguration, "/api/temp_configuration")
api.add_resource(Root, "/api") api.add_resource(Root, "/api")
api.add_resource(Registration, "/api/registration") api.add_resource(Registration, "/api/registration")
api.add_resource(Authenticate, "/api/auth") api.add_resource(Authenticate, "/api/auth")

View File

@ -0,0 +1,57 @@
from dataclasses import dataclass
import flask_restful
from common.utils.exceptions import (
InvalidConfigurationError,
InvalidCredentialsError,
NoCredentialsError,
)
from monkey_island.cc.resources.auth.auth import jwt_required
@dataclass
class ResponseContents:
import_status: str = "imported"
message: str = ""
status_code: int = 200
def form_response(self):
return self.__dict__, self.status_code
# TODO remove once backend implementation is done
class TempConfiguration(flask_restful.Resource):
SUCCESS = False
@jwt_required
def post(self):
# request_contents = json.loads(request.data)
try:
self.decrypt()
self.import_config()
return ResponseContents().form_response()
except InvalidCredentialsError:
return ResponseContents(
import_status="wrong_password", message="Wrong password supplied", status_code=403
).form_response()
except InvalidConfigurationError:
return ResponseContents(
import_status="invalid_configuration",
message="Invalid configuration supplied. "
"Maybe the format is outdated or the file is malformed",
status_code=400,
).form_response()
except NoCredentialsError:
return ResponseContents(
import_status="password_required",
message="Configuration file is protected with a password. "
"Please enter the password",
status_code=403,
).form_response()
def decrypt(self):
return False
def import_config(self):
return True

View File

@ -1,8 +1,11 @@
import {Button, Modal, Form} from 'react-bootstrap'; import {Button, Modal, Form} from 'react-bootstrap';
import React, {useState} from 'react'; import React, {useEffect, useState} from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
import '../../styles/components/configuration-components/ExportConfigModal.scss'; import '../../styles/components/configuration-components/ImportConfigModal.scss';
import {faCheck, faCross} from '@fortawesome/free-solid-svg-icons';
type Props = { type Props = {
@ -10,29 +13,85 @@ type Props = {
onClick: () => void onClick: () => void
} }
const ConfigImportModal = (props: Props) => {
// TODO implement the back end
const configExportEndpoint = '/api/configuration/export';
const [pass, setPass] = useState(''); const UploadStatuses = {
const [radioValue, setRadioValue] = useState('password'); clean: 'clean',
const authComponent = new AuthComponent({}); success: 'success',
error: 'error'
function isExportBtnDisabled() {
return pass === '' && radioValue === 'password';
} }
function onSubmit() {
authComponent.authFetch(configExportEndpoint, const UploadStatusIcon = (props: { status: string }) => {
switch (props.status) {
case UploadStatuses.success:
return (<FontAwesomeIcon icon={faCheck} className={'success'}/>);
case UploadStatuses.error:
return (<FontAwesomeIcon icon={faCross} className={'error'}/>);
default:
return null;
}
}
// TODO add types
const ConfigImportModal = (props: Props) => {
// TODO implement the back end
const configImportEndpoint = '/api/temp_configuration';
const [uploadStatus, setUploadStatus] = useState(UploadStatuses.clean);
const [importDisabled, setImportDisabled] = useState(true);
const [configContents, setConfigContents] = useState('');
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const authComponent = new AuthComponent({});
useEffect(() => {
if (configContents !== '') {
sendConfigToServer();
}
}, [configContents])
function sendConfigToServer() {
authComponent.authFetch(configImportEndpoint,
{ {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ body: JSON.stringify({
should_encrypt: (radioValue === 'password'), config: configContents,
password: pass password: password
}) })
} }
) ).then(res => res.json())
.then(res => {
if (res['import_status'] === 'password_required') {
setShowPassword(true);
} else if (res['import_status'] === 'wrong_password'){
setErrorMessage(res['message']);
}
if (res['import_status'] === 'invalid_configuration'){
setUploadStatus(UploadStatuses.error);
setErrorMessage(res['message']);
} else {
setUploadStatus(UploadStatuses.success);
}
if (res.status == 200) {
setImportDisabled(false);
}
})
}
function uploadFile(event) {
let reader = new FileReader();
reader.onload = (event) => {
setConfigContents(JSON.stringify(event.target.result))
};
reader.readAsText(event.target.files[0]);
event.target.value = null;
}
function onImportClick() {
} }
return ( return (
@ -47,16 +106,20 @@ const ConfigImportModal = (props: Props) => {
<div key={'config-export-option'} <div key={'config-export-option'}
className={`mb-3 export-type-radio-buttons`}> className={`mb-3 export-type-radio-buttons`}>
<Form> <Form>
<Form.File id="exampleFormControlFile1" <Form.File id='exampleFormControlFile1'
label="Example file input" /> label='Please choose a configuration file'
accept='.conf'
onChange={uploadFile}/>
<UploadStatusIcon status={uploadStatus}/>
{showPassword && <PasswordInput onChange={setPassword} />}
</Form> </Form>
</div> </div>
</Modal.Body> </Modal.Body>
<Modal.Footer> <Modal.Footer>
<Button variant={'info'} <Button variant={'info'}
onClick={onSubmit} onClick={onImportClick}
disabled={isExportBtnDisabled()}> disabled={importDisabled}>
Import Import
</Button> </Button>
</Modal.Footer> </Modal.Footer>
@ -64,7 +127,7 @@ const ConfigImportModal = (props: Props) => {
} }
const PasswordInput = (props: { const PasswordInput = (props: {
onChange: (passValue) => void onChange: (passValue) => void,
}) => { }) => {
return ( return (
<div className={'config-export-password-input'}> <div className={'config-export-password-input'}>
@ -76,29 +139,5 @@ const PasswordInput = (props: {
) )
} }
const ExportPlaintextChoiceField = (props: {
radioValue: string,
onChange: (radioValue) => void
}) => {
return (
<div className={'config-export-plaintext'}>
<Form.Check
type={'radio'}
label={'Skip encryption (export as plaintext)'}
name={'export-choice'}
value={'plaintext'}
checked={props.radioValue === 'plaintext'}
onChange={evt => {
props.onChange(evt.target.value);
}}
/>
<p className={`export-warning text-secondary`}>
Configuration might contain stolen credentials or sensitive data.<br/>
It is advised to use password encryption option.
</p>
</div>
)
}
export default ConfigImportModal; export default ConfigImportModal;

View File

@ -358,6 +358,7 @@ class ConfigurePageComponent extends AuthComponent {
this.authFetch(apiEndpoint, request_options); this.authFetch(apiEndpoint, request_options);
} }
// TODO remove after import implementation
setConfigOnImport = (event) => { setConfigOnImport = (event) => {
try { try {
var newConfig = JSON.parse(event.target.result); var newConfig = JSON.parse(event.target.result);
@ -377,6 +378,7 @@ class ConfigurePageComponent extends AuthComponent {
); );
} }
// TODO remove after import implementation
setConfigFromImportCandidate(){ setConfigFromImportCandidate(){
this.setState({ this.setState({
configuration: this.state.importCandidateConfig, configuration: this.state.importCandidateConfig,
@ -388,6 +390,7 @@ class ConfigurePageComponent extends AuthComponent {
this.currentFormData = {}; this.currentFormData = {};
} }
// TODO remove after export implementation
exportConfig = () => { exportConfig = () => {
this.updateConfigSection(); this.updateConfigSection();
const configAsJson = JSON.stringify(this.state.configuration, null, 2); const configAsJson = JSON.stringify(this.state.configuration, null, 2);
@ -415,6 +418,7 @@ class ConfigurePageComponent extends AuthComponent {
})); }));
} }
// TODO remove after import implementation
importConfig = (event) => { importConfig = (event) => {
let reader = new FileReader(); let reader = new FileReader();
reader.onload = this.setConfigOnImport; reader.onload = this.setConfigOnImport;

View File

@ -0,0 +1,30 @@
.config-export-modal .config-export-password-input p {
display: inline-block;
width: auto;
margin-top: 0;
margin-bottom: 0;
margin-right: 10px;
}
.config-export-modal .export-type-radio-buttons
.password-radio-button .config-export-password-input input {
display: inline-block;
width: auto;
top: 0;
transform: none;
}
.config-export-modal .export-type-radio-buttons .password-radio-button input{
margin-top: 0;
top: 50%;
-ms-transform: translateY(-50%);
transform: translateY(-50%);
}
.config-export-modal div.config-export-plaintext p.export-warning {
margin-left: 20px;
}
.config-export-modal div.config-export-plaintext {
margin-top: 15px;
}