Added interactive AWS key setup/scoutsuite configuration

This commit is contained in:
VakarisZ 2020-10-01 15:02:54 +03:00
parent 708d1a697d
commit dd3d5d317a
21 changed files with 474 additions and 43 deletions

View File

@ -24,3 +24,7 @@ class AlreadyRegisteredError(RegistrationNotNeededError):
class RulePathCreatorNotFound(Exception):
""" Raise to indicate that ScoutSuite rule doesn't have a path creator"""
class InvalidAWSKeys(Exception):
""" Raise to indicate that AWS API keys are invalid"""

View File

@ -47,6 +47,8 @@ from monkey_island.cc.resources.test.monkey_test import MonkeyTest
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.scoutsuite_auth.aws_keys import AWSKeys
from monkey_island.cc.resources.zero_trust.scoutsuite_auth.scoutsuite_auth import ScoutSuiteAuth
from monkey_island.cc.services.database import Database
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
from monkey_island.cc.services.representations import output_json
@ -146,6 +148,8 @@ def init_api_resources(api):
api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/')
api.add_resource(RemotePortCheck, '/api/monkey_control/check_remote_port/<string:port>')
api.add_resource(StartedOnIsland, '/api/monkey_control/started_on_island')
api.add_resource(ScoutSuiteAuth, '/api/scoutsuite_auth/<string:provider>')
api.add_resource(AWSKeys, '/api/aws_keys')
api.add_resource(MonkeyTest, '/api/test/monkey')
api.add_resource(ClearCaches, '/api/test/clear_caches')

View File

@ -0,0 +1,11 @@
import flask_restful
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import get_aws_keys
class AWSKeys(flask_restful.Resource):
@jwt_required
def get(self):
return get_aws_keys()

View File

@ -0,0 +1,34 @@
import json
import flask_restful
from flask import request
from common.cloud.scoutsuite_consts import PROVIDERS
from common.utils.exceptions import InvalidAWSKeys
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import is_cloud_authentication_setup, \
set_aws_keys
class ScoutSuiteAuth(flask_restful.Resource):
@jwt_required
def get(self, provider: PROVIDERS):
if provider == PROVIDERS.AWS.value:
is_setup, message = is_cloud_authentication_setup(provider)
return {'is_setup': is_setup, 'message': message}
else:
return {'is_setup': False, 'message': ''}
@jwt_required
def post(self, provider: PROVIDERS):
key_info = json.loads(request.data)
error_msg = ''
if provider == PROVIDERS.AWS.value:
try:
set_aws_keys(access_key_id=key_info['accessKeyId'],
secret_access_key=key_info['secretAccessKey'],
session_token=key_info['sessionToken'])
except InvalidAWSKeys as e:
error_msg = str(e)
return {'error_msg': error_msg}

View File

@ -69,7 +69,7 @@ class LoginPageComponent extends React.Component {
<Form className={'auth-form'} onSubmit={this.login}>
<Form.Control onChange={evt => this.updateUsername(evt)} type='text' placeholder='Username'/>
<Form.Control onChange={evt => this.updatePassword(evt)} type='password' placeholder='Password'/>
<Button id={'auth-button'} type={'submit'}>
<Button className={'monkey-submit-button'} type={'submit'}>
Login
</Button>
{

View File

@ -92,7 +92,7 @@ class RegisterPageComponent extends React.Component {
<Form className={'auth-form'} onSubmit={this.register} >
<Form.Control onChange={evt => this.updateUsername(evt)} type='text' placeholder='Username'/>
<Form.Control onChange={evt => this.updatePassword(evt)} type='password' placeholder='Password'/>
<Button id={'auth-button'} type={'submit'} >
<Button className={'monkey-submit-button'} type={'submit'} >
Let's go!
</Button>
<Row>

View File

@ -7,7 +7,6 @@ import InlineSelection from '../../ui-components/inline-selection/InlineSelectio
import {cloneDeep} from 'lodash';
import {faCloud, faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons';
import RunOnIslandButton from './RunOnIslandButton';
import AWSSetup from './scoutsuite-setup/AWSSetup';
import CloudOptions from './scoutsuite-setup/CloudOptions';
function RunOptions(props) {

View File

@ -1,16 +1,17 @@
import {Button} from 'react-bootstrap';
import React from 'react';
import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
import CloudOptions from './CloudOptions';
import {COLUMN_SIZES} from '../../../ui-components/inline-selection/utils';
import '../../../../styles/components/scoutsuite/AWSSetup.scss';
import InlineSelection from '../../../../ui-components/inline-selection/InlineSelection';
import {COLUMN_SIZES} from '../../../../ui-components/inline-selection/utils';
import '../../../../../styles/components/scoutsuite/AWSSetup.scss';
import AWSSetupOptions from './AWSSetupOptions';
export default function AWSSetup(props) {
export default function AWSCLISetup(props) {
return InlineSelection(getContents, {
...props,
collumnSize: COLUMN_SIZES.LARGE,
onBackButtonClick: () => {
props.setComponent(CloudOptions, props)
props.setComponent(AWSSetupOptions, props);
}
})
}
@ -33,9 +34,11 @@ const getContents = (props) => {
<li>
2. Run <code>aws configure</code>. It's important to configure credentials, which
allows ScoutSuite to get information about your cloud configuration. The most trivial way to do so is to
provide <Button
provide
<Button
href={'https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-creds'}
variant={'link'}>
variant={'link'}
target={'_blank'}>
Access key ID and secret access key
</Button>.
</li>

View File

@ -0,0 +1,176 @@
import React, {useEffect, useState} from 'react';
import InlineSelection from '../../../../ui-components/inline-selection/InlineSelection';
import {COLUMN_SIZES} from '../../../../ui-components/inline-selection/utils';
import AWSSetupOptions from './AWSSetupOptions';
import {Button, Col, Form, Row} from 'react-bootstrap';
import AuthComponent from '../../../../AuthComponent';
import '../../../../../styles/components/scoutsuite/AWSSetup.scss';
import {PROVIDERS} from '../ProvidersEnum';
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 {faQuestion} from '@fortawesome/free-solid-svg-icons';
import Collapse from '@kunukn/react-collapse/dist/Collapse.umd';
import keySetupForAnyUserImage from '../../../../../images/aws_keys_tutorial-any-user.png';
import keySetupForCurrentUserImage from '../../../../../images/aws_keys_tutorial-current-user.png';
import ImageModal from '../../../../ui-components/ImageModal';
export default function AWSCLISetup(props) {
return InlineSelection(getContents, {
...props,
collumnSize: COLUMN_SIZES.LARGE,
onBackButtonClick: () => {
props.setComponent(AWSSetupOptions, props);
}
})
}
const authComponent = new AuthComponent({})
const getContents = (props) => {
const [accessKeyId, setAccessKeyId] = useState('');
const [secretAccessKey, setSecretAccessKey] = useState('');
const [sessionToken, setSessionToken] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const [successMessage, setSuccessMessage] = useState('');
const [docCollapseOpen, setDocCollapseOpen] = useState(false);
function submitKeys(event) {
event.preventDefault();
setSuccessMessage('');
setErrorMessage('');
authComponent.authFetch(
'/api/scoutsuite_auth/' + PROVIDERS.AWS,
{
'method': 'POST',
'body': JSON.stringify({
'accessKeyId': accessKeyId,
'secretAccessKey': secretAccessKey,
'sessionToken': sessionToken
})
})
.then(res => res.json())
.then(res => {
if (res['error_msg'] === '') {
setSuccessMessage('AWS keys saved!');
} else {
setErrorMessage(res['error_msg']);
}
});
}
useEffect(() => {
authComponent.authFetch('/api/aws_keys')
.then(res => res.json())
.then(res => {
setAccessKeyId(res['access_key_id']);
setSecretAccessKey(res['secret_access_key']);
setSessionToken(res['session_token']);
});
}, [props]);
// TODO separate into standalone component
function getKeyCreationDocsContent() {
return (
<div className={'key-creation-tutorial'}>
<h5>Tips</h5>
<p>Consider creating a new user account just for this activity. Assign only <b>ReadOnlyAccess</b> and&nbsp;
<b>SecurityAudit</b> policies.</p>
<h5>Keys for custom user</h5>
<p>1. Open the IAM console at <a href={'https://console.aws.amazon.com/iam/'}
target={'_blank'}>https://console.aws.amazon.com/iam/</a> .</p>
<p>2. In the navigation pane, choose Users.</p>
<p>3. Choose the name of the user whose access keys you want to create, and then choose the Security credentials
tab.</p>
<p>4. In the Access keys section, choose Create access key.</p>
<p>To view the new access key pair, choose Show. Your credentials will look something like this:</p>
<p>Access key ID: AKIAIOSFODNN7EXAMPLE</p>
<p>Secret access key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY</p>
<Row>
<Col lg={3} md={3} sm={5} xs={12}>
<ImageModal image={keySetupForAnyUserImage}/>
</Col>
</Row>
<h5>Keys for current user</h5>
<p>1. Click on your username in the upper right corner.</p>
<p>2. Click on "My security credentials".</p>
<p>3. In the Access keys section, choose Create access key.</p>
<p>To view the new access key pair, choose Show. Your credentials will look something like this:</p>
<p>Access key ID: AKIAIOSFODNN7EXAMPLE</p>
<p>Secret access key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY</p>
<Row>
<Col lg={3} md={3} sm={5} xs={12}>
<ImageModal image={keySetupForCurrentUserImage}/>
</Col>
</Row>
</div>);
}
function getKeyCreationDocs() {
return (
<div className={classNames('collapse-item', {'item--active': docCollapseOpen})}>
<button className={'btn-collapse'}
onClick={() => setDocCollapseOpen(!docCollapseOpen)}>
<span>
<FontAwesomeIcon icon={faQuestion} className={'question-icon'}/>
<p>How to generate keys</p>
</span>
<span>
<FontAwesomeIcon icon={docCollapseOpen ? faChevronDown : faChevronUp}/>
</span>
</button>
<Collapse
className='collapse-comp'
isOpen={docCollapseOpen}
render={getKeyCreationDocsContent}/>
</div>);
}
return (
<div className={'aws-scoutsuite-key-configuration'}>
{getKeyCreationDocs()}
<Form className={'auth-form'} onSubmit={submitKeys}>
<Form.Control onChange={evt => setAccessKeyId(evt.target.value)}
type='text'
placeholder='Access key ID'
value={accessKeyId}/>
<Form.Control onChange={evt => setSecretAccessKey(evt.target.value)}
type='password'
placeholder='Secret access key'
value={secretAccessKey}/>
<Form.Control onChange={evt => setSessionToken(evt.target.value)}
type='text'
placeholder='Session token (optional, only for temp. keys)'
value={sessionToken}/>
{
errorMessage ?
<div className="alert alert-danger" role="alert">{errorMessage}</div>
:
''
}
{
successMessage ?
<div className="alert alert-success" role="alert">{successMessage}&nbsp;
Go back and&nbsp;
<Button variant={'link'} onClick={() => props.setComponent()} className={'link-in-success-message'}>
run Monkey from the Island server </Button> to start AWS scan!</div>
:
''
}
<Row className={'justify-content-center'}>
<Col lg={4} md={6} sm={8} xs={12}>
<Button className={'monkey-submit-button'} type={'submit'}>
Submit
</Button>
</Col>
</Row>
</Form>
</div>
);
}

View File

@ -0,0 +1,40 @@
import React from 'react';
import InlineSelection from '../../../../ui-components/inline-selection/InlineSelection';
import NextSelectionButton from '../../../../ui-components/inline-selection/NextSelectionButton';
import {faKey, faTerminal} from '@fortawesome/free-solid-svg-icons';
import AWSCLISetup from './AWSCLISetup';
import CloudOptions from '../CloudOptions';
import AWSKeySetup from './AWSKeySetup';
const AWSSetupOptions = (props) => {
return InlineSelection(getContents, {
...props,
onBackButtonClick: () => {
props.setComponent(CloudOptions, props);
}
})
}
const getContents = (props) => {
return (
<>
<NextSelectionButton title={'Security keys'}
description={'Provide security keys for monkey to authenticate.'}
icon={faKey}
onButtonClick={() => {
props.setComponent(AWSKeySetup,
{setComponent: props.setComponent})
}}/>
<NextSelectionButton title={'AWS CLI'}
description={'Manually configure AWS CLI yourself.'}
icon={faTerminal}
onButtonClick={() => {
props.setComponent(AWSCLISetup,
{setComponent: props.setComponent})
}}/>
</>
)
}
export default AWSSetupOptions;

View File

@ -1,8 +1,10 @@
import React from 'react';
import React, {useEffect, useState} from 'react';
import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
import NextSelectionButton from '../../../ui-components/inline-selection/NextSelectionButton';
import {faCloud} from '@fortawesome/free-solid-svg-icons';
import AWSSetup from './AWSSetup';
import {faCheck, faCloud, faSync} from '@fortawesome/free-solid-svg-icons';
import AWSSetupOptions from './AWSConfiguration/AWSSetupOptions';
import {PROVIDERS} from './ProvidersEnum';
import AuthComponent from '../../../AuthComponent';
const CloudOptions = (props) => {
@ -14,14 +16,38 @@ const CloudOptions = (props) => {
})
}
const authComponent = new AuthComponent({})
const getContents = (props) => {
const [description, setDescription] = useState("Loading...");
const [iconType, setIconType] = useState('spinning-icon');
const [icon, setIcon] = useState(faSync);
useEffect(() => {
authComponent.authFetch('/api/scoutsuite_auth/' + PROVIDERS.AWS)
.then(res => res.json())
.then(res => {
if(res.is_setup){
setDescription(res.message + 'Click next to change the configuration.');
setIconType('icon-success');
setIcon(faCheck);
} else {
setDescription('Setup Amazon Web Services infrastructure scan.');
setIconType('')
setIcon(faCloud);
}
});
}, [props]);
return (
<>
<NextSelectionButton title={'AWS'}
description={'Setup Amazon Web Services infrastructure scan.'}
icon={faCloud}
description={description}
icon={icon}
iconType={iconType}
onButtonClick={() => {
props.setComponent(AWSSetup,
props.setComponent(AWSSetupOptions,
{setComponent: props.setComponent})
}}/>
</>

View File

@ -0,0 +1,33 @@
import React, {useState} from 'react';
import PropTypes from 'prop-types';
import {Button, Image, Modal} from 'react-bootstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faSearchPlus} from '@fortawesome/free-solid-svg-icons';
const ImageModal = (props) => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div className={'image-modal'}>
<Button className={'image-modal-thumbnail'} onClick={() => setIsModalOpen(true)}>
<FontAwesomeIcon icon={faSearchPlus} className={'image-modal-thumbnail-icon'}/>
<Image src={props.image} thumbnail fluid/>
</Button>
<Modal show={isModalOpen}
className={'image-modal-screen'}
onHide={() => setIsModalOpen(false)}>
<Modal.Body>
<Image src={props.image} fluid />
</Modal.Body>
</Modal>
</div>
);
}
export default ImageModal;
ImageModal.propTypes = {
image: PropTypes.element
}

View File

@ -18,8 +18,8 @@ export default function InlineSelection(WrappedComponent, props) {
)
}
function renderBackButton(props){
if(props.onBackButtonClick !== undefined){
function renderBackButton(props) {
if (props.onBackButtonClick !== undefined) {
return (<BackButton onClick={props.onBackButtonClick}/>);
}
}

View File

@ -6,7 +6,8 @@ import {faAngleRight} from '@fortawesome/free-solid-svg-icons';
export default function nextSelectionButton(props) {
let description = props.description !== undefined ? (<p>{props.description}</p>) : ''
let icon = props.icon !== undefined ? (<FontAwesomeIcon icon={props.icon}/>) : ''
let iconType = props.iconType !== undefined ? props.iconType : ''
let icon = props.icon !== undefined ? (<FontAwesomeIcon className={iconType} icon={props.icon}/>) : ''
return (
<Row>
<Col>
@ -24,6 +25,7 @@ export default function nextSelectionButton(props) {
nextSelectionButton.propTypes = {
title: PropTypes.string,
iconType: PropTypes.string,
icon: FontAwesomeIcon,
description: PropTypes.string,
onButtonClick: PropTypes.func

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

View File

@ -3,6 +3,7 @@
@import '../../node_modules/bootstrap/scss/bootstrap';
// Imports that require variables
@import 'components/Buttons';
@import 'pages/report/ReportPage.scss';
@import 'pages/report/AttackReport.scss';
@import 'pages/ConfigurationPage';
@ -13,11 +14,12 @@
@import 'components/AdvancedMultiSelect';
@import 'components/particle-component/ParticleBackground';
@import 'components/scoutsuite/ResourceDropdown';
@import 'components/ImageModal';
@import 'components/Icons';
@import 'components/inline-selection/InlineSelection';
@import 'components/inline-selection/NextSelectionButton';
@import 'components/inline-selection/BackButton';
@import 'components/inline-selection/CommandDisplay';
@import 'components/Icons';
// Define custom elements after bootstrap import

View File

@ -3,6 +3,14 @@
display: inline-block;
}
.icon-success {
color: $success
}
.icon-failed {
color: $danger;
}
@keyframes spin-animation {
0% {
transform: rotate(0deg);

View File

@ -0,0 +1,49 @@
.image-modal .image-modal-thumbnail {
position: relative;
padding: 7px;
}
.image-modal .image-modal-thumbnail:focus {
background-color: white;
border-color: white;
box-shadow: 0 2px 6px #ccc;
}
.image-modal .image-modal-thumbnail:hover {
background-color: white;
border-color: white;
box-shadow: 0 2px 6px #ccc;
}
.image-modal .image-modal-thumbnail-icon {
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%);
-moz-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
position: absolute;
min-width: 40px;
min-height: 40px;
}
.image-modal:hover .image-modal-thumbnail-icon {
color: $monkey-yellow;
}
.image-modal-screen {
padding: 0;
}
.image-modal-screen .modal-dialog {
margin: 0;
top: 0;
width: 100%;
padding: 30px;
max-width: none;
max-height: none;
}
.image-modal-screen .modal-dialog .modal-content {
width: fit-content;
margin: auto;
}

View File

@ -1,17 +1,74 @@
.aws-scoutsuite-configuration a{
.aws-scoutsuite-configuration a {
display: inline-block;
padding: 0 0 3px 0;
}
.aws-scoutsuite-configuration ol{
.aws-scoutsuite-configuration ol {
padding-left: 15px;
margin-bottom: 30px;
}
.aws-scoutsuite-configuration ol.nested-ol{
.aws-scoutsuite-configuration ol.nested-ol {
margin-bottom: 0;
}
.aws-scoutsuite-configuration li{
.aws-scoutsuite-configuration li {
margin-bottom: 5px;
}
.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

@ -15,23 +15,6 @@
margin-top: 20px;
}
#auth-button {
border-color: #3f3f3f;
font-size: 1.3em;
width: 100%;
background-color: #3f3f3f;
color: $monkey-yellow;
}
#auth-button:hover {
border-color: $monkey-yellow;
font-size: 1.3em;
font-weight: bold;
width: 100%;
background-color: $monkey-yellow;
color: #000000;
}
.monkey-detective {
max-height: 500px;
}