UI: Remove Scoutsuite reporting

This commit is contained in:
Shreya Malviya 2022-02-03 15:57:44 +05:30
parent 9dc0a6ed6f
commit c0d1df6252
16 changed files with 8 additions and 693 deletions

View File

@ -71,7 +71,7 @@ class ReportPageComponent extends AuthComponent {
}
getZeroTrustReportFromServer = async () => {
let ztReport = {findings: {}, principles: {}, pillars: {}, scoutsuite_data: {}};
let ztReport = {findings: {}, principles: {}, pillars: {}};
await this.authFetch('/api/report/zero-trust/findings')
.then(res => res.json())
.then(res => {
@ -87,11 +87,6 @@ class ReportPageComponent extends AuthComponent {
.then(res => {
ztReport.pillars = res;
});
await this.authFetch('/api/report/zero-trust/scoutsuite')
.then(res => res.json())
.then(res => {
ztReport.scoutsuite_data = res;
});
return ztReport
};

View File

@ -30,8 +30,7 @@ class ZeroTrustReportPageComponent extends AuthComponent {
<PrinciplesSection principles={this.state.principles}
pillarsToStatuses={this.state.pillars.pillarsToStatuses}/>
<FindingsSection pillarsToStatuses={this.state.pillars.pillarsToStatuses}
findings={this.state.findings}
scoutsuite_data={this.state.scoutsuite_data}/>
findings={this.state.findings}/>
</div>;
}
@ -59,8 +58,7 @@ class ZeroTrustReportPageComponent extends AuthComponent {
stillLoadingDataFromServer() {
return typeof this.state.findings === 'undefined'
|| typeof this.state.pillars === 'undefined'
|| typeof this.state.principles === 'undefined'
|| typeof this.state.scoutsuite_data === 'undefined';
|| typeof this.state.principles === 'undefined';
}

View File

@ -33,13 +33,10 @@ class FindingsSection extends Component {
</p>
<FindingsTable data={findingsByStatus[ZeroTrustStatuses.failed]}
scoutsuite_data={this.props.scoutsuite_data}
status={ZeroTrustStatuses.failed}/>
<FindingsTable data={findingsByStatus[ZeroTrustStatuses.verify]}
scoutsuite_data={this.props.scoutsuite_data}
status={ZeroTrustStatuses.verify}/>
<FindingsTable data={findingsByStatus[ZeroTrustStatuses.passed]}
scoutsuite_data={this.props.scoutsuite_data}
status={ZeroTrustStatuses.passed}/>
</div>
);

View File

@ -4,7 +4,6 @@ import PaginatedTable from '../common/PaginatedTable';
import * as PropTypes from 'prop-types';
import PillarLabel from './PillarLabel';
import EventsButton from './EventsButton';
import ScoutSuiteRuleButton from './scoutsuite/ScoutSuiteRuleButton';
const EVENTS_COLUMN_MAX_WIDTH = 180;
const PILLARS_COLUMN_MAX_WIDTH = 260;
@ -36,16 +35,11 @@ export class FindingsTable extends Component {
];
getFindingDetails(finding) {
if ('scoutsuite_rules' in finding.details) {
return <ScoutSuiteRuleButton scoutsuite_rules={finding.details.scoutsuite_rules}
scoutsuite_data={this.props.scoutsuite_data}/>;
} else {
return <EventsButton finding_id={finding.finding_id}
latest_events={finding.details.latest_events}
oldest_events={finding.details.oldest_events}
event_count={finding.details.event_count}
exportFilename={'Events_' + finding.test_key}/>;
}
return <EventsButton finding_id={finding.finding_id}
latest_events={finding.details.latest_events}
oldest_events={finding.details.oldest_events}
event_count={finding.details.event_count}
exportFilename={'Events_' + finding.test_key}/>;
}
getFindingPillars(finding) {

View File

@ -1,84 +0,0 @@
import React, {useState} from 'react';
import * as PropTypes from 'prop-types';
import '../../../../styles/components/scoutsuite/RuleDisplay.scss'
import classNames from 'classnames';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown';
import {faChevronUp} from '@fortawesome/free-solid-svg-icons/faChevronUp';
import ScoutSuiteDataParser from './ScoutSuiteDataParser';
import Collapse from '@kunukn/react-collapse';
import {faArrowRight} from '@fortawesome/free-solid-svg-icons';
export default function ResourceDropdown(props) {
const [isCollapseOpen, setIsCollapseOpen] = useState(false);
let parser = new ScoutSuiteDataParser(props.scoutsuite_data.data.services);
let resource_value = parser.getResourceValue(props.resource_path, props.template_path);
function getResourceDropdown() {
return (
<div key={props.resource_path} className={classNames('collapse-item',
'resource-collapse', {'item--active': isCollapseOpen})}>
<button className={'btn-collapse'}
onClick={() => setIsCollapseOpen(!isCollapseOpen)}>
<span>
{Object.prototype.hasOwnProperty.call(resource_value, 'name') ?
resource_value.name : props.resource_path}
</span>
<span>
<FontAwesomeIcon icon={isCollapseOpen ? faChevronDown : faChevronUp}/>
</span>
</button>
<Collapse
className='collapse-comp'
isOpen={isCollapseOpen}
render={getResourceDropdownContents}/>
</div>
);
}
function replacePathDotsWithArrows(resourcePath) {
let path_vars = resourcePath.split('.')
let display_path = []
for (let i = 0; i < path_vars.length; i++) {
display_path.push(path_vars[i])
if (i !== path_vars.length - 1) {
display_path.push(<FontAwesomeIcon icon={faArrowRight} key={'arrow-' + i}/>)
}
}
return display_path;
}
function prettyPrintJson(data) {
return JSON.stringify(data, null, 4);
}
function getResourceValueDisplay() {
return (
<div>
<p className={'resource-value-title'}>Value:</p>
<pre className={'resource-value-json'}>{prettyPrintJson(resource_value)}</pre>
</div>
);
}
function getResourceDropdownContents() {
return (
<div className={'resource-display'}>
<div>
<p className={'resource-path-title'}>Path:</p>
<p className={'resource-path-contents'}>{replacePathDotsWithArrows(props.resource_path)}</p>
</div>
{getResourceValueDisplay()}
</div>
);
}
return getResourceDropdown();
}
ResourceDropdown.propTypes = {
template_path: PropTypes.string,
resource_path: PropTypes.string,
scoutsuite_data: PropTypes.object
};

View File

@ -1,70 +0,0 @@
import React from 'react';
import * as PropTypes from 'prop-types';
import '../../../../styles/components/scoutsuite/RuleDisplay.scss'
import ResourceDropdown from './ResourceDropdown';
export default function RuleDisplay(props) {
return (
<div className={'scoutsuite-rule-display'}>
<div className={'description'}>
<h3>{props.rule.description}({props.rule.service})</h3>
</div>
<div className={'rationale'}>
<p dangerouslySetInnerHTML={{__html: props.rule.rationale}}/>
</div>
<div className={'checked-resources'}>
<p className={'checked-resources-title'}>Resources checked: </p>
<p>{props.rule.checked_items}</p>
</div>
{getReferences()}
{getResources()}
</div>);
function getReferences() {
let references = []
props.rule.references.forEach(reference => {
references.push(<a href={reference}
className={'reference-link'}
target={'_blank'}
rel="noopener noreferrer"
key={reference}>{reference}</a>)
})
if (references.length) {
return (
<div className={'reference-list'}>
<p className={'reference-list-title'}>References:</p>
{references}
</div>)
} else {
return null;
}
}
function getResources() {
let resources = []
for (let i = 0; i < props.rule.items.length; i++) {
let item = props.rule.items[i];
let template_path = Object.prototype.hasOwnProperty.call(props.rule, 'display_path')
? props.rule.display_path : props.rule.path;
resources.push(<ResourceDropdown resource_path={item}
template_path={template_path}
scoutsuite_data={props.scoutsuite_data}
key={template_path + i}/>)
}
if (resources.length) {
return (
<div className={'reference-list'}>
<p className={'reference-list-title'}>Flagged resources (<b>{props.rule.flagged_items}</b>):</p>
{resources}
</div>)
} else {
return null;
}
}
}
RuleDisplay.propTypes = {
rule: PropTypes.object,
scoutsuite_data: PropTypes.object
};

View File

@ -1,118 +0,0 @@
export default class ScoutSuiteDataParser {
constructor(runResults) {
this.runResults = runResults
}
/**
* Gets value of cloud resource based on path of specific checked field and more abstract template path,
* which describes the scope of resource values.
* @param itemPath contains path to a specific value e.g. s3.buckets.da1e7081077ce92.secure_transport_enabled
* @param templatePath contains a template path for resource we would want to display e.g. s3.buckets.id
* @returns {*[]|*} resource value e.g. {'bucket_id': 123, 'bucket_max_size': '123GB'}
*/
getResourceValue(itemPath, templatePath) {
let resourcePath = this.fillTemplatePath(itemPath, templatePath);
return this.getObjectValueByPath(resourcePath, this.runResults);
}
/**
* Replaces id's in template path with id's from item path to form actual path to the object
* @param itemPath e.g. s3.buckets.da1e7081077ce92.secure_transport_enabled
* @param templatePath e.g. s3.buckets.id
* @returns {*} e.g. s3.buckets.da1e7081077ce92
*/
fillTemplatePath(itemPath, templatePath) {
let itemPathArray = itemPath.split('.');
let templatePathArray = templatePath.split('.');
let resourcePathArray = templatePathArray.map((val, i) => {
return val === 'id' ? itemPathArray[i] : val
})
return resourcePathArray.join('.');
}
/**
* Retrieves value from ScoutSuite data object based on path, provided in the rule
* @param path E.g. a.id.c.id.e
* @param source E.g. {a: {b: {c: {d: {e: [{result1: 'result1'}, {result2: 'result2'}]}}}}}
* @returns {*[]|*} E.g. ['result1', 'result2']
*/
getObjectValueByPath(path, source) {
let key;
while (path) {
key = this.getNextKeyInPath(path);
source = this.getValueForKey(key, path, source);
path = this.trimFirstKey(path);
}
return source;
}
/**
* Gets next key from the path
* @param path e.g. s3.buckets.id
* @returns {string|*} s3
*/
getNextKeyInPath(path) {
if (path.indexOf('.') !== -1) {
return path.substr(0, path.indexOf('.'));
} else {
return path;
}
}
/**
* Returns value from object, based on path and current key
* @param key E.g. "a"
* @param path E.g. "a.b.c"
* @param source E.g. {a: {b: {c: 'result'}}}
* @returns {[]|*} E.g. {b: {c: 'result'}}
*/
getValueForKey(key, path, source) {
if (key === 'id') {
return this.getValueByReplacingUnknownKey(path, source);
} else {
return source[key];
}
}
/**
* Gets value from object if first key in path doesn't match source object
* @param path unknown.b.c
* @param source {a: {b: {c: [{result:'result'}]}}}
* @returns {[]} 'result'
*/
getValueByReplacingUnknownKey(path, source) {
let value = [];
for (let key in source) {
value = this.getObjectValueByPath(this.replaceFirstKey(path, key), source);
value = value.concat(Object.values(value));
}
return value;
}
/**
* Replaces first key in path
* @param path E.g. "one.two.three"
* @param replacement E.g. "four"
* @returns string E.g. "four.two.three"
*/
replaceFirstKey(path, replacement) {
return replacement + path.substr(path.indexOf('.'), path.length);
}
/**
* Trims the first key from dot separated path.
* @param path E.g. "one.two.three"
* @returns {string|boolean} E.g. "two.three"
*/
trimFirstKey(path) {
if (path.indexOf('.') !== -1) {
return path.substr(path.indexOf('.') + 1, path.length);
} else {
return false;
}
}
}

View File

@ -1,46 +0,0 @@
import React, {Component} from 'react';
import {Button} from 'react-bootstrap';
import * as PropTypes from 'prop-types';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faList} from '@fortawesome/free-solid-svg-icons/faList';
import ScoutSuiteRuleModal from './ScoutSuiteRuleModal';
import CountBadge from '../../../ui-components/CountBadge';
export default class ScoutSuiteRuleButton extends Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false
}
}
toggleModal = () => {
this.setState({isModalOpen: !this.state.isModalOpen});
};
render() {
return (
<>
<ScoutSuiteRuleModal scoutsuite_rules={this.props.scoutsuite_rules}
scoutsuite_data={this.props.scoutsuite_data}
isModalOpen={this.state.isModalOpen}
hideCallback={this.toggleModal} />
<div className="text-center" style={{'display': 'grid'}}>
<Button variant={'monkey-info'} size={'lg'} onClick={this.toggleModal}>
<FontAwesomeIcon icon={faList}/> Rules
&nbsp;<CountBadge count={this.props.scoutsuite_rules.length}/>
</Button>
</div>
</>);
}
createRuleCountBadge() {
}
}
ScoutSuiteRuleButton.propTypes = {
scoutsuite_rules: PropTypes.array,
scoutsuite_data: PropTypes.object
};

View File

@ -1,94 +0,0 @@
import React, {useState} from 'react';
import {Modal} from 'react-bootstrap';
import * as PropTypes from 'prop-types';
import Pluralize from 'pluralize';
import ScoutSuiteSingleRuleDropdown from './ScoutSuiteSingleRuleDropdown';
import '../../../../styles/components/scoutsuite/RuleModal.scss';
import STATUSES from '../../common/consts/StatusConsts';
import {getRuleCountByStatus, sortRules} from './rule-parsing/ParsingUtils';
export default function ScoutSuiteRuleModal(props) {
const [openRuleId, setOpenRuleId] = useState(null)
function toggleRuleDropdown(ruleId) {
let ruleIdToSet = (openRuleId === ruleId) ? null : ruleId;
setOpenRuleId(ruleIdToSet);
}
function renderRuleDropdowns() {
let dropdowns = [];
let rules = sortRules(props.scoutsuite_rules);
rules.forEach(rule => {
let dropdown = (<ScoutSuiteSingleRuleDropdown isCollapseOpen={openRuleId === rule.description}
toggleCallback={() => toggleRuleDropdown(rule.description)}
rule={rule}
scoutsuite_data={props.scoutsuite_data}
key={rule.description + rule.path}/>)
dropdowns.push(dropdown)
});
return dropdowns;
}
function getGeneralRuleOverview() {
return <>
There {Pluralize('is', props.scoutsuite_rules.length)}
&nbsp;<span className={'badge badge-primary'}>{props.scoutsuite_rules.length}</span>
&nbsp;ScoutSuite {Pluralize('rule', props.scoutsuite_rules.length)} associated with this finding.
</>
}
function getFailedRuleOverview() {
let failedRuleCnt = getRuleCountByStatus(props.scoutsuite_rules, STATUSES.STATUS_FAILED) +
+ getRuleCountByStatus(props.scoutsuite_rules, STATUSES.STATUS_VERIFY);
return <>
&nbsp;<span className={'badge badge-danger'}>{failedRuleCnt}</span>
&nbsp;failed security {Pluralize('rule', failedRuleCnt)}.
</>
}
function getPassedRuleOverview() {
let passedRuleCnt = getRuleCountByStatus(props.scoutsuite_rules, STATUSES.STATUS_PASSED);
return <>
&nbsp;<span className={'badge badge-success'}>{passedRuleCnt}</span>
&nbsp;passed security {Pluralize('rule', passedRuleCnt)}.
</>
}
function getUnexecutedRuleOverview() {
let unexecutedRuleCnt = getRuleCountByStatus(props.scoutsuite_rules, STATUSES.STATUS_UNEXECUTED);
return <>
&nbsp;<span className={'badge badge-default'}>{unexecutedRuleCnt}</span>
&nbsp;{Pluralize('rule', unexecutedRuleCnt)} {Pluralize('was', unexecutedRuleCnt)} not
checked (no relevant resources for the rule).
</>
}
return (
<div>
<Modal show={props.isModalOpen} onHide={() => props.hideCallback()} className={'scoutsuite-rule-modal'}>
<Modal.Body>
<h3>
<div className="text-center">ScoutSuite rules</div>
</h3>
<hr/>
<p>
{getGeneralRuleOverview()}
{getFailedRuleOverview()}
{getPassedRuleOverview()}
{getUnexecutedRuleOverview()}
</p>
{renderRuleDropdowns()}
</Modal.Body>
</Modal>
</div>
);
}
ScoutSuiteRuleModal.propTypes = {
isModalOpen: PropTypes.bool,
scoutsuite_rules: PropTypes.array,
scoutsuite_data: PropTypes.object,
hideCallback: PropTypes.func
};

View File

@ -1,79 +0,0 @@
import React from 'react';
import Collapse from '@kunukn/react-collapse';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
import {faChevronUp} from '@fortawesome/free-solid-svg-icons/faChevronUp'
import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown'
import classNames from 'classnames';
import * as PropTypes from 'prop-types';
import STATUSES from '../../common/consts/StatusConsts';
import {faCheckCircle, faCircle, faExclamationCircle} from '@fortawesome/free-solid-svg-icons';
import RuleDisplay from './RuleDisplay';
import {getRuleStatus} from './rule-parsing/ParsingUtils';
export default function ScoutSuiteSingleRuleDropdown(props) {
function getRuleCollapse() {
return (
<div className={classNames('collapse-item',
'rule-collapse', {'item--active': props.isCollapseOpen})}>
<button className={classNames('btn-collapse', getDropdownClass())}
onClick={props.toggleCallback}>
<span>
<FontAwesomeIcon icon={getRuleIcon()}/>
{props.rule.description}
</span>
<span>
<FontAwesomeIcon icon={props.isCollapseOpen ? faChevronDown : faChevronUp}/>
</span>
</button>
<Collapse
className='collapse-comp'
isOpen={props.isCollapseOpen}
render={renderRule}/>
</div>
);
}
function getRuleIcon() {
let ruleStatus = getRuleStatus(props.rule);
switch (ruleStatus) {
case STATUSES.STATUS_PASSED:
return faCheckCircle;
case STATUSES.STATUS_VERIFY:
return faExclamationCircle;
case STATUSES.STATUS_FAILED:
return faExclamationCircle;
case STATUSES.STATUS_UNEXECUTED:
return faCircle;
}
}
function getDropdownClass() {
let ruleStatus = getRuleStatus(props.rule);
switch (ruleStatus) {
case STATUSES.STATUS_PASSED:
return 'collapse-success';
case STATUSES.STATUS_VERIFY:
return 'collapse-danger';
case STATUSES.STATUS_FAILED:
return 'collapse-danger';
case STATUSES.STATUS_UNEXECUTED:
return 'collapse-default';
}
}
function renderRule() {
return <RuleDisplay rule={props.rule} scoutsuite_data={props.scoutsuite_data}/>
}
return getRuleCollapse();
}
ScoutSuiteSingleRuleDropdown.propTypes = {
isCollapseOpen: PropTypes.bool,
rule: PropTypes.object,
scoutsuite_data: PropTypes.object,
toggleCallback: PropTypes.func
};

View File

@ -1,40 +0,0 @@
import STATUSES from '../../../common/consts/StatusConsts';
import RULE_LEVELS from '../../../common/consts/ScoutSuiteConsts/RuleLevels';
export function getRuleStatus(rule) {
if (rule.checked_items === 0) {
return STATUSES.STATUS_UNEXECUTED
} else if (rule.items.length === 0) {
return STATUSES.STATUS_PASSED
} else if (rule.level === RULE_LEVELS.LEVEL_WARNING) {
return STATUSES.STATUS_VERIFY
} else {
return STATUSES.STATUS_FAILED
}
}
export function getRuleCountByStatus(rules, status) {
return rules.filter(rule => getRuleStatus(rule) === status).length;
}
export function sortRules(rules) {
rules.sort(compareRules);
return rules;
}
function compareRules(firstRule, secondRule) {
let firstStatus = getRuleStatus(firstRule);
let secondStatus = getRuleStatus(secondRule);
return compareRuleStatuses(firstStatus, secondStatus);
}
function compareRuleStatuses(ruleStatusOne, ruleStatusTwo) {
const severity_order = {
[STATUSES.STATUS_FAILED]: 1,
[STATUSES.STATUS_VERIFY]: 2,
[STATUSES.STATUS_PASSED]: 3,
[STATUSES.STATUS_UNEXECUTED]: 4
}
return severity_order[ruleStatusOne] - severity_order[ruleStatusTwo]
}

View File

@ -13,7 +13,6 @@
@import 'components/PreviewPane';
@import 'components/AdvancedMultiSelect';
@import 'components/particle-component/ParticleBackground';
@import 'components/scoutsuite/ResourceDropdown';
@import 'components/ImageModal';
@import 'components/Icons';
@import 'components/inline-selection/InlineSelection';

View File

@ -1,86 +0,0 @@
.aws-scoutsuite-configuration a {
display: inline-block;
padding: 0 0 3px 0;
}
.aws-scoutsuite-configuration ol {
padding-left: 15px;
margin-bottom: 30px;
}
.aws-scoutsuite-configuration ol.nested-ol {
margin-bottom: 0;
}
.aws-scoutsuite-configuration li {
margin-bottom: 0;
}
.aws-scoutsuite-configuration h2 {
margin-bottom: 20px;
}
.aws-scoutsuite-configuration p {
margin-bottom: 5px;
}
.aws-scoutsuite-configuration .cli-link {
padding: 0 0 4px 0;
}
.monkey-submit-button {
margin-bottom: 15px;
}
.aws-scoutsuite-key-configuration .collapse-item {
padding: 0;
margin-bottom: 15px;
}
.aws-scoutsuite-key-configuration .collapse-item .btn-collapse .question-icon {
display: inline-block;
margin-right: 7px;
margin-bottom: 1px;
}
.aws-scoutsuite-key-configuration .collapse-item .btn-collapse p {
display: inline-block;
margin-bottom: 0;
font-size: 1.2em;
margin-left: 5px
}
.aws-scoutsuite-key-configuration .key-creation-tutorial {
padding-bottom: 10px;
}
.aws-scoutsuite-key-configuration .key-creation-tutorial p {
margin-bottom: 2px;
font-weight: 400;
}
.aws-scoutsuite-key-configuration .key-creation-tutorial h5 {
margin-top: 15px;
font-weight: 600;
}
.aws-scoutsuite-key-configuration .key-creation-tutorial p:first-child {
margin-top: 15px;
}
.aws-scoutsuite-key-configuration .image-modal {
margin-top: 5px;
}
.aws-scoutsuite-key-configuration .key-creation-tutorial img {
max-width: 100%;
max-height: 100%;
border: 1px solid black;
}
.link-in-success-message {
padding: 0 !important;
vertical-align: initial !important;
}

View File

@ -1,21 +0,0 @@
.resource-display {
margin-top: 10px;
}
.resource-display .resource-value-json {
background-color: $gray-200;
padding: 4px;
}
.resource-display .resource-path-contents svg {
margin-left: 5px;
margin-right: 5px;
width: 10px;
}
.resource-display .resource-value-title,
.resource-display .resource-path-title {
margin-right:5px;
font-weight: 500;
margin-bottom: 0;
}

View File

@ -1,21 +0,0 @@
.scoutsuite-rule-display .description h3{
font-size: 1.2em;
margin-top: 10px;
}
.scoutsuite-rule-display p{
display: inline-block;
}
.scoutsuite-rule-display .checked-resources-title,
.scoutsuite-rule-display .flagged-resources-title,
.scoutsuite-rule-display .reference-list-title{
font-weight: 500;
margin-right: 5px;
margin-bottom: 0;
}
.scoutsuite-rule-display .reference-list a {
display: block;
margin-left: 10px;
}

View File

@ -1,9 +0,0 @@
.scoutsuite-rule-modal .modal-dialog {
max-width: 1000px;
top: 0;
padding: 30px;
}
.collapse-item.rule-collapse button > span:nth-child(2) {
flex: 1
}