Added interactive AWS key setup/scoutsuite configuration
This commit is contained in:
parent
708d1a697d
commit
dd3d5d317a
|
@ -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"""
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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()
|
|
@ -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}
|
|
@ -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>
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
href={'https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-creds'}
|
||||
variant={'link'}>
|
||||
provide
|
||||
<Button
|
||||
href={'https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-creds'}
|
||||
variant={'link'}
|
||||
target={'_blank'}>
|
||||
Access key ID and secret access key
|
||||
</Button>.
|
||||
</li>
|
|
@ -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
|
||||
<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}
|
||||
Go back and
|
||||
<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>
|
||||
);
|
||||
}
|
|
@ -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;
|
|
@ -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})
|
||||
}}/>
|
||||
</>
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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}/>);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 |
|
@ -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
|
||||
|
|
|
@ -3,6 +3,14 @@
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
.icon-success {
|
||||
color: $success
|
||||
}
|
||||
|
||||
.icon-failed {
|
||||
color: $danger;
|
||||
}
|
||||
|
||||
@keyframes spin-animation {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue