forked from p15670423/monkey
Merge remote-tracking branch 'origin/refactor-advanced-multiselect' into fix_swimm_units
This commit is contained in:
commit
bdf1dc2213
|
@ -22,17 +22,18 @@
|
|||
"hunkComments": []
|
||||
},
|
||||
"hunkDiffLines": [
|
||||
"@@ -62,15 +62,7 @@",
|
||||
"@@ -68,16 +68,7 @@",
|
||||
" \"Removes the file afterwards.\",",
|
||||
" \"attack_techniques\": [\"T1166\"]",
|
||||
" },",
|
||||
"- {",
|
||||
"+ # Swimmer: ADD DETAILS HERE!\",",
|
||||
"+ # Swimmer: ADD DETAILS HERE!",
|
||||
"- \"type\": \"string\",",
|
||||
"- \"enum\": [",
|
||||
"- \"ScheduleJobs\"",
|
||||
"- ],",
|
||||
"- \"title\": \"Job scheduling\",",
|
||||
"- \"safe\": True,",
|
||||
"- \"info\": \"Attempts to create a scheduled job on the system and remove it.\",",
|
||||
"- \"attack_techniques\": [\"T1168\", \"T1053\"]",
|
||||
"- },",
|
||||
|
@ -45,5 +46,9 @@
|
|||
}
|
||||
},
|
||||
"app_version": "0.3.5-1",
|
||||
"file_version": "1.0.4"
|
||||
"file_version": "1.0.4",
|
||||
"hunksOrder": [
|
||||
"monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py_0"
|
||||
],
|
||||
"last_commit_sha_for_swimm_patch": "9d9e8168fb2c23367b9947273aa1a041687b3e2e"
|
||||
}
|
|
@ -23,26 +23,29 @@
|
|||
"hunkComments": []
|
||||
},
|
||||
"hunkDiffLines": [
|
||||
"@@ -10,12 +10,6 @@",
|
||||
"@@ -10,11 +10,5 @@",
|
||||
" \"\"\"",
|
||||
" ",
|
||||
" def __init__(self):",
|
||||
"- linux_cmds, windows_cmds = get_commands_to_schedule_jobs()",
|
||||
"+ pass",
|
||||
" ",
|
||||
"- super(ScheduleJobs, self).__init__(name=POST_BREACH_JOB_SCHEDULING,",
|
||||
"-",
|
||||
"+ # Swimmer: IMPLEMENT HERE!",
|
||||
"- super(ScheduleJobs, self).__init__(name=POST_BREACH_JOB_SCHEDULING,",
|
||||
"- linux_cmd=' '.join(linux_cmds),",
|
||||
"- windows_cmd=windows_cmds)",
|
||||
"- ",
|
||||
"- def run(self):",
|
||||
"- super(ScheduleJobs, self).run()",
|
||||
"- remove_scheduled_jobs()"
|
||||
"- super(ScheduleJobs, self).run()"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"app_version": "0.3.5-1",
|
||||
"file_version": "1.0.4"
|
||||
"file_version": "1.0.4",
|
||||
"hunksOrder": [
|
||||
"monkey/infection_monkey/post_breach/actions/schedule_jobs.py_0"
|
||||
],
|
||||
"last_commit_sha_for_swimm_patch": "44fd1ab69cfbab33cec638dcbbaa8831992a9a9f"
|
||||
}
|
|
@ -81,7 +81,7 @@
|
|||
"@@ -1,5 +1,5 @@",
|
||||
" from common.data.post_breach_consts import (",
|
||||
"- POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER)",
|
||||
"+ POST_BREACH_BACKDOOR_USER)",
|
||||
"+ POST_BREACH_COMMUNICATE_AS_NEW_USER)",
|
||||
" from monkey_island.cc.services.attack.technique_reports.pba_technique import \\",
|
||||
" PostBreachTechnique",
|
||||
" "
|
||||
|
@ -97,7 +97,7 @@
|
|||
" scanned_msg = \"Monkey tried creating a new user on the network's systems, but failed.\"",
|
||||
" used_msg = \"Monkey created a new user on the network's systems.\"",
|
||||
"- pba_names = [POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER]",
|
||||
"+ pba_names = [POST_BREACH_BACKDOOR_USER]"
|
||||
"+ pba_names = [POST_BREACH_COMMUNICATE_AS_NEW_USER]"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -111,7 +111,7 @@
|
|||
"hunkComments": []
|
||||
},
|
||||
"hunkDiffLines": [
|
||||
"@@ -4,15 +4,7 @@",
|
||||
"@@ -4,16 +4,7 @@",
|
||||
" \"might do after breaching a new machine. Used in ATT&CK and Zero trust reports.\",",
|
||||
" \"type\": \"string\",",
|
||||
" \"anyOf\": [",
|
||||
|
@ -122,6 +122,7 @@
|
|||
"- \"BackdoorUser\"",
|
||||
"- ],",
|
||||
"- \"title\": \"Back door user\",",
|
||||
"- \"safe\": True,",
|
||||
"- \"info\": \"Attempts to create a new user on the system and delete it afterwards.\",",
|
||||
"- \"attack_techniques\": [\"T1136\"]",
|
||||
"- },",
|
||||
|
@ -134,5 +135,13 @@
|
|||
}
|
||||
},
|
||||
"app_version": "0.3.5-1",
|
||||
"file_version": "1.0.4"
|
||||
"file_version": "1.0.4",
|
||||
"hunksOrder": [
|
||||
"monkey/common/data/post_breach_consts.py_0",
|
||||
"monkey/infection_monkey/post_breach/actions/add_user.py_0",
|
||||
"monkey/monkey_island/cc/services/attack/technique_reports/T1136.py_0",
|
||||
"monkey/monkey_island/cc/services/attack/technique_reports/T1136.py_1",
|
||||
"monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py_0"
|
||||
],
|
||||
"last_commit_sha_for_swimm_patch": "9d9e8168fb2c23367b9947273aa1a041687b3e2e"
|
||||
}
|
20
.travis.yml
20
.travis.yml
|
@ -60,23 +60,20 @@ before_script:
|
|||
|
||||
script:
|
||||
# Check Python code
|
||||
## Check syntax errors and fail the build if any are found.
|
||||
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
|
||||
- flake8 ./monkey --config=./ci_scripts/flake8_syntax_check.ini
|
||||
|
||||
## Warn about linter issues.
|
||||
### --exit-zero forces Flake8 to use the exit status code 0 even if there are errors, which means this will NOT fail the build.
|
||||
### --count will print the total number of errors.
|
||||
### --statistics Count the number of occurrences of each error/warning code and print a report.
|
||||
### The output is redirected to a file.
|
||||
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics > flake8_warnings.txt
|
||||
- flake8 ./monkey --exit-zero --config=./ci_scripts/flake8_linter_check.ini > ./ci_scripts/flake8_warnings.txt
|
||||
## Display the linter issues
|
||||
- cat flake8_warnings.txt
|
||||
- cat ./ci_scripts/flake8_warnings.txt
|
||||
## Make sure that we haven't increased the amount of warnings.
|
||||
- PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT=80
|
||||
- if [ $(tail -n 1 flake8_warnings.txt) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT ]; then echo "Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. " && exit 1; fi
|
||||
- if [ $(tail -n 1 ./ci_scripts/flake8_warnings.txt) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT ]; then echo "Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. " && exit 1; fi
|
||||
|
||||
## Check import order
|
||||
- python -m isort . -c -p common -p infection_monkey -p monkey_island
|
||||
- python -m isort ./monkey --settings-file ./ci_scripts/isort.cfg
|
||||
|
||||
## Run unit tests
|
||||
- cd monkey # This is our source dir
|
||||
|
@ -98,10 +95,11 @@ script:
|
|||
|
||||
# verify swimm
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
- wget https://firebasestorage.googleapis.com/v0/b/swimmio.appspot.com/o/Release%2Fv018%2Fswimm-0.1.8-linux-executable\?alt\=media\&token\=e59c0a18-577f-4b77-bb3b-91b22c3d8b2a -O swimm
|
||||
- wget "https://firebasestorage.googleapis.com/v0/b/swimmio.appspot.com/o/Release%2Fv029%2FSwimm_0.2.9_Setup.deb?alt=media&token=774ebd98-cb4e-4615-900c-aada224c1608" -O swimm
|
||||
- sudo dpkg -i swimm || (sudo apt-get update && sudo apt-get -f install)
|
||||
- chmod +x ./swimm
|
||||
- ./swimm --version
|
||||
- ./swimm verify
|
||||
- swimm --version
|
||||
- swimm verify
|
||||
|
||||
after_success:
|
||||
# Upload code coverage results to codecov.io, see https://github.com/codecov/codecov-bash for more information
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
./validation-env
|
||||
./flake8_warnings.txt
|
|
@ -0,0 +1,8 @@
|
|||
# About
|
||||
|
||||
Run this script to validate your code locally and auto fix/format the problems before pushing.
|
||||
|
||||
# Usage
|
||||
|
||||
You've got to manually download swimm for swimm validation.
|
||||
run from `infection_monkey` directory: `powershell .\ci_scripts\validate.ps1`
|
|
@ -0,0 +1,15 @@
|
|||
[flake8]
|
||||
## Warn about linter issues.
|
||||
|
||||
exclude = ../monkey/monkey_island/cc/ui,
|
||||
../monkey/common/cloud
|
||||
show-source = True
|
||||
max-complexity = 10
|
||||
max-line-length = 127
|
||||
|
||||
### --statistics Count the number of occurrences of each error/warning code and print a report.
|
||||
statistics = True
|
||||
|
||||
### --count will print the total number of errors.
|
||||
count = True
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
[flake8]
|
||||
|
||||
## Check syntax errors and fail the build if any are found.
|
||||
exclude =
|
||||
../monkey/monkey_island/cc/ui,
|
||||
../monkey/common/cloud
|
||||
select =
|
||||
E901,
|
||||
E999,
|
||||
F821,
|
||||
F822,
|
||||
F823
|
||||
count = True
|
||||
show-source = True
|
||||
statistics = True
|
|
@ -0,0 +1,5 @@
|
|||
python -m venv validation-env
|
||||
.\validation-env\Scripts\activate.ps1
|
||||
python -m pip install -r .\requirements.txt
|
||||
npm i -g eslint
|
||||
deactivate
|
|
@ -0,0 +1,6 @@
|
|||
[isort]
|
||||
|
||||
# Possible options: https://pycqa.github.io/isort/docs/configuration/options/
|
||||
|
||||
known_first_party=common,infection_monkey,monkey_island
|
||||
skip=monkey/common/cloud/scoutsuite,monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py,monkey/monkey_island/cc/ui,monkey/common/cloud/scoutsuite
|
|
@ -0,0 +1,6 @@
|
|||
flake8
|
||||
pytest
|
||||
dlint
|
||||
isort
|
||||
coverage
|
||||
black
|
|
@ -0,0 +1,39 @@
|
|||
.\ci_scripts\validation-env\Scripts\activate.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
python -m pip install -r monkey/monkey_island/requirements.txt
|
||||
python -m pip install -r monkey/infection_monkey/requirements.txt
|
||||
flake8 ./monkey --config ./ci_scripts/flake8_syntax_check.cfg
|
||||
flake8 ./monkey --exit-zero --config ./ci_scripts/flake8_linter_check.cfg | Out-File -FilePath .\ci_scripts\flake8_warnings.txt
|
||||
Get-Content -Path .\ci_scripts\flake8_warnings.txt
|
||||
$PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT = 80
|
||||
if ((Get-Item -Path .\ci_scripts\flake8_warnings.txt | Get-Content -Tail 1) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT){
|
||||
"Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. "
|
||||
exit
|
||||
}
|
||||
python -m isort ./monkey -c --settings-file ./ci_scripts/isort.cfg
|
||||
if (!$?) {
|
||||
$confirmation = Read-Host "Isort found errors. Do you want to attmpt to fix them automatically? (y/n)"
|
||||
if ($confirmation -eq 'y') {
|
||||
python -m isort ./monkey --settings-file ./ci_scripts/isort.cfg
|
||||
}
|
||||
}
|
||||
Push-Location -Path ./monkey
|
||||
python ./monkey_island/cc/environment/set_server_config.py testing
|
||||
python -m pytest
|
||||
$lastCommandSucceeded = $?
|
||||
python ./monkey_island/cc/environment/set_server_config.py restore
|
||||
Pop-Location
|
||||
|
||||
if (!$lastCommandSucceeded) {
|
||||
exit
|
||||
}
|
||||
|
||||
Push-Location -Path .\monkey\monkey_island\cc\ui
|
||||
eslint ./src -c ./.eslintrc
|
||||
Pop-Location
|
||||
|
||||
swimm verify
|
||||
|
||||
Write-Host "Script finished. Press any key to continue"
|
||||
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');
|
||||
deactivate
|
|
@ -39,6 +39,7 @@ Your user must have root permissions; however, don't run the script as root!
|
|||
|
||||
```sh
|
||||
wget https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/deploy_linux.sh
|
||||
chmod u+x ./deploy_linux.sh
|
||||
```
|
||||
|
||||
This will download our deploy script. It's a good idea to read it quickly before executing it!
|
||||
|
@ -53,3 +54,12 @@ After downloading that script, execute it in a shell. The first argument should
|
|||
- `./deploy_linux.sh "/home/user/new" "master"` (if directory "new" is not found creates it and clones master branch into it)
|
||||
|
||||
You may also pass in an optional third `false` parameter to disable downloading the latest agent binaries.
|
||||
|
||||
### Run on Linux
|
||||
|
||||
After the `deploy_linux.sh` script completes, you can start the monkey island.
|
||||
|
||||
```sh
|
||||
cd infection_monkey/monkey
|
||||
./monkey_island/linux/run.sh
|
||||
```
|
||||
|
|
|
@ -4,41 +4,42 @@ export MONKEY_FOLDER_NAME="infection_monkey"
|
|||
# Url of public git repository that contains monkey's source code
|
||||
export MONKEY_GIT_URL="https://github.com/guardicore/monkey"
|
||||
|
||||
exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
get_latest_release() {
|
||||
curl --silent "https://api.github.com/repos/$1/releases/latest" | # Get latest release from GitHub API
|
||||
RELEASE_URL="https://api.github.com/repos/$1/releases/latest"
|
||||
|
||||
if exists wget; then
|
||||
RELEASE_INFO=$(wget --quiet -O - "$RELEASE_URL") # Get latest release from GitHub API
|
||||
else
|
||||
RELEASE_INFO=$(curl --silent "$RELEASE_URL") # Get latest release from GitHub API
|
||||
fi
|
||||
|
||||
echo "$RELEASE_INFO" |
|
||||
grep '"tag_name":' | # Get tag line
|
||||
sed -E 's/.*"([^"]+)".*/\1/' # Pluck JSON value
|
||||
}
|
||||
|
||||
MONKEY_LATEST_RELEASE=$(get_latest_release "monkey/guardicore")
|
||||
MONKEY_LATEST_RELEASE=$(get_latest_release "guardicore/monkey")
|
||||
|
||||
# Monkey binaries
|
||||
LINUX_32_BINARY_NAME="monkey-linux-32"
|
||||
LINUX_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/monkey-linux-32"
|
||||
export LINUX_32_BINARY_URL
|
||||
export LINUX_32_BINARY_NAME
|
||||
export LINUX_32_BINARY_NAME="monkey-linux-32"
|
||||
export LINUX_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-linux-32"
|
||||
|
||||
LINUX_64_BINARY_NAME="monkey-linux-64"
|
||||
LINUX_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/monkey-linux-64"
|
||||
export LINUX_64_BINARY_URL
|
||||
export LINUX_64_BINARY_NAME
|
||||
export LINUX_64_BINARY_NAME="monkey-linux-64"
|
||||
export LINUX_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-linux-64"
|
||||
|
||||
WINDOWS_32_BINARY_NAME="monkey-windows-32.exe"
|
||||
WINDOWS_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/monkey-windows-32.exe"
|
||||
export WINDOWS_32_BINARY_URL
|
||||
export WINDOWS_32_BINARY_NAME
|
||||
export WINDOWS_32_BINARY_NAME="monkey-windows-32.exe"
|
||||
export WINDOWS_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-windows-32.exe"
|
||||
|
||||
WINDOWS_64_BINARY_NAME="monkey-windows-64.exe"
|
||||
WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/monkey-windows-64.exe"
|
||||
export WINDOWS_64_BINARY_URL
|
||||
export WINDOWS_64_BINARY_NAME
|
||||
export WINDOWS_64_BINARY_NAME="monkey-windows-64.exe"
|
||||
export WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-windows-64.exe"
|
||||
|
||||
# Other binaries for monkey
|
||||
TRACEROUTE_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/traceroute64"
|
||||
export TRACEROUTE_64_BINARY_URL
|
||||
TRACEROUTE_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/traceroute32"
|
||||
export TRACEROUTE_32_BINARY_URL
|
||||
SAMBACRY_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/sc_monkey_runner64.so"
|
||||
export SAMBACRY_64_BINARY_URL
|
||||
SAMBACRY_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/sc_monkey_runner32.so"
|
||||
export SAMBACRY_32_BINARY_URL
|
||||
export TRACEROUTE_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/traceroute64"
|
||||
export TRACEROUTE_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/traceroute32"
|
||||
|
||||
export SAMBACRY_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/sc_monkey_runner64.so"
|
||||
export SAMBACRY_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/sc_monkey_runner32.so"
|
||||
|
|
|
@ -10,7 +10,7 @@ is_root() {
|
|||
|
||||
has_sudo() {
|
||||
# 0 true, 1 false
|
||||
timeout 1 sudo id && return 0 || return 1
|
||||
return $(sudo -nv > /dev/null 2>&1)
|
||||
}
|
||||
|
||||
handle_error() {
|
||||
|
@ -23,6 +23,11 @@ log_message() {
|
|||
echo -e "DEPLOYMENT SCRIPT: $1"
|
||||
}
|
||||
|
||||
if is_root; then
|
||||
log_message "Please don't run this script as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
config_branch=${2:-"develop"}
|
||||
config_url="https://raw.githubusercontent.com/guardicore/monkey/${config_branch}/deployment_scripts/config"
|
||||
|
||||
|
@ -62,14 +67,9 @@ ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries"
|
|||
INFECTION_MONKEY_DIR="$monkey_home/monkey/infection_monkey"
|
||||
MONKEY_BIN_DIR="$INFECTION_MONKEY_DIR/bin"
|
||||
|
||||
if is_root; then
|
||||
log_message "Please don't run this script as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HAS_SUDO=$(has_sudo)
|
||||
if [[ ! $HAS_SUDO ]]; then
|
||||
log_message "You need root permissions for some of this script operations. Quiting."
|
||||
if ! has_sudo; then
|
||||
log_message "You need root permissions for some of this script operations. \
|
||||
Run \`sudo -v\`, enter your password, and then re-run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
@ -110,13 +110,16 @@ if [[ ${python_cmd} == "" ]]; then
|
|||
log_message "Python 3.7 command not found. Installing python 3.7."
|
||||
sudo add-apt-repository ppa:deadsnakes/ppa
|
||||
sudo apt-get update
|
||||
sudo apt install python3.7 python3.7-dev
|
||||
sudo apt-get install -y python3.7 python3.7-dev
|
||||
log_message "Python 3.7 is now available with command 'python3.7'."
|
||||
python_cmd="python3.7"
|
||||
fi
|
||||
|
||||
log_message "Installing build-essential"
|
||||
sudo apt install build-essential
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
log_message "Installing python3-distutils"
|
||||
sudo apt-get install -y python3-distutils
|
||||
|
||||
log_message "Installing or updating pip"
|
||||
# shellcheck disable=SC2086
|
||||
|
@ -134,7 +137,7 @@ requirements_island="$ISLAND_PATH/requirements.txt"
|
|||
${python_cmd} -m pip install -r "${requirements_island}" --user --upgrade || handle_error
|
||||
|
||||
log_message "Installing monkey requirements"
|
||||
sudo apt-get install libffi-dev upx libssl-dev libc++1
|
||||
sudo apt-get install -y libffi-dev upx libssl-dev libc++1
|
||||
requirements_monkey="$INFECTION_MONKEY_DIR/requirements.txt"
|
||||
${python_cmd} -m pip install -r "${requirements_monkey}" --user --upgrade || handle_error
|
||||
|
||||
|
@ -162,15 +165,19 @@ chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_64_BINARY_NAME"
|
|||
|
||||
# If a user haven't installed mongo manually check if we can install it with our script
|
||||
if ! exists mongod; then
|
||||
log_message "Installing libcurl4"
|
||||
sudo apt-get install -y libcurl4
|
||||
|
||||
log_message "Installing MongoDB"
|
||||
"${ISLAND_PATH}"/linux/install_mongo.sh ${MONGO_PATH} || handle_error
|
||||
fi
|
||||
log_message "Installing openssl"
|
||||
sudo apt-get install openssl
|
||||
sudo apt-get install -y openssl
|
||||
|
||||
# Generate SSL certificate
|
||||
log_message "Generating certificate"
|
||||
|
||||
chmod u+x "${ISLAND_PATH}"/linux/create_certificate.sh
|
||||
"${ISLAND_PATH}"/linux/create_certificate.sh ${ISLAND_PATH}/cc
|
||||
|
||||
# Update node
|
||||
|
|
|
@ -9,6 +9,7 @@ Here are some of the most common questions we receive about the Infection Monkey
|
|||
|
||||
- [Where can I get the latest Monkey version? 📰](#where-can-i-get-the-latest-monkey-version)
|
||||
- [How long does a single Monkey run for? Is there a time limit?](#how-long-does-a-single-monkey-run-for-is-there-a-time-limit)
|
||||
- [How to reset the password?](#how-to-reset-the-password)
|
||||
- [Should I run the Monkey continuously?](#should-i-run-the-monkey-continuously)
|
||||
- [Which queries does Monkey perform to the Internet exactly?](#which-queries-does-monkey-perform-to-the-internet-exactly)
|
||||
- [Where can I find the log files of the Monkey and the Monkey Island, and how can I read them?](#where-can-i-find-the-log-files-of-the-monkey-and-the-monkey-island-and-how-can-i-read-them)
|
||||
|
@ -35,6 +36,23 @@ If you want to see what has changed between versions, refer to the [releases pag
|
|||
|
||||
The Monkey shuts off either when it can't find new victims, or when it has exceeded the quota of victims as defined in the configuration.
|
||||
|
||||
## How to reset the password?
|
||||
|
||||
On your first access of Monkey Island server, you'll be prompted to create an account. If you forgot the credentials you
|
||||
entered or just want to change them, you need to manually alter the `server_config.json` file. On Linux, this file is
|
||||
located on `/var/monkey/monkey_island/cc/server_config.json`. On windows, it's based on your install directory (typically
|
||||
`C:\Program Files\Guardicore\Monkey Island\monkey_island\cc\server_config.json`). Reset the contents of this file
|
||||
leaving the **deployment option unchanged** (it might be "vmware" or "linux" in your case):
|
||||
|
||||
```json
|
||||
{
|
||||
"server_config": "password",
|
||||
"deployment": "windows"
|
||||
}
|
||||
```
|
||||
Then reset the Island process (`sudo systemctl restart monkey-island.service` for linux, restart program for windows).
|
||||
Finally, go to the Island's URL and create a new account.
|
||||
|
||||
## Should I run the Monkey continuously?
|
||||
|
||||
Yes! This will allow you to verify that no new security issues were identified by the Monkey since the last time you ran it.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
+++
|
||||
title = "Reports"
|
||||
date = 2020-06-24T21:16:03+03:00
|
||||
weight = 5
|
||||
weight = 40
|
||||
chapter = true
|
||||
pre = "<i class='fas fa-scroll'></i> "
|
||||
+++
|
||||
|
@ -10,4 +10,4 @@ pre = "<i class='fas fa-scroll'></i> "
|
|||
|
||||
The Monkey offers three reports:
|
||||
|
||||
{{% children %}}
|
||||
{{% children description=true style="p"%}}
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: "MITRE ATT&CK report"
|
||||
description: "Maps the Monkey's actions to the MITRE ATT&CK knowledge base"
|
||||
date: 2020-06-24T21:17:18+03:00
|
||||
draft: false
|
||||
---
|
|
@ -2,6 +2,7 @@
|
|||
title: "Security report"
|
||||
date: 2020-06-24T21:16:10+03:00
|
||||
draft: false
|
||||
description: "Provides actionable recommendations and insight into an attacker's view of your network"
|
||||
---
|
||||
|
||||
{{% notice info %}}
|
|
@ -2,6 +2,7 @@
|
|||
title: "Zero Trust report"
|
||||
date: 2020-06-24T21:16:18+03:00
|
||||
draft: false
|
||||
description: "Generates a status report with detailed explanations of Zero Trust security gaps and prescriptive instructions on how to rectify them"
|
||||
---
|
||||
|
||||
{{% notice info %}}
|
||||
|
@ -17,7 +18,7 @@ This diagram provides a quick glance at how your organization scores on each com
|
|||
- {{< label danger Failed >}} At least one of the tests related to this component failed. This means that the Infection Monkey detected an unmet Zero Trust requirement.
|
||||
- {{< label warning Verify >}} At least one of the tests’ results related to this component requires further manual verification.
|
||||
- {{< label success Passed >}} All Tests related to this pillar passed. No violation of a Zero Trust guiding principle was detected.
|
||||
- {{< label other Unexecuted >}} This status means no tests were executed for this pillar.
|
||||
- {{< label unused Unexecuted >}} This status means no tests were executed for this pillar.
|
||||
|
||||
![Zero Trust Report summary](/images/usage/reports/ztreport1.png "Zero Trust Report summary")
|
||||
|
|
@ -54,16 +54,15 @@ See [Amazon's documentation about working with SSM agents](https://docs.aws.amaz
|
|||
|
||||
### Running the monkey
|
||||
|
||||
When you run the monkey island on an AWS instance, the island detects it's running on AWS and present the following option in the _"Run Monkey"_ page, like so:
|
||||
When you run the Monkey Island on an AWS instance, the island detects it's running on AWS and present the following option in the _"Run Monkey"_ page, like so:
|
||||
|
||||
![Running a Monkey on EC2 Instance](/images/usage/integrations/monkey-island-aws-screenshot-1.png "Running a Monkey on EC2 Instance")
|
||||
|
||||
And then you can choose one of the available instances as "patient zero" like so:
|
||||
After you click on "AWS run" you can choose one of the available instances as "patient zero" like so:
|
||||
|
||||
1. Click on "Run on AWS"
|
||||
2. Choose the relevant Network Interface
|
||||
3. Select the machines you'd like to run the Monkey on
|
||||
4. Click "Run on Selected Machines", and watch the monkey go! 🐒
|
||||
1. Choose the relevant Network Interface
|
||||
2. Select the machines you'd like to run the Monkey on
|
||||
3. Click "Run on Selected Machines", and watch the monkey go! 🐒
|
||||
|
||||
![Running a Monkey on EC2 Instance](/images/usage/integrations/monkey-island-aws-screenshot-2.png "Running a Monkey on EC2 Instance")
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 72 KiB |
Binary file not shown.
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 97 KiB |
Binary file not shown.
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 101 KiB |
|
@ -228,7 +228,6 @@ class ShellShockExploiter(HostExploiter):
|
|||
Checks if which urls exist
|
||||
:return: Sequence of URLs to try and attack
|
||||
"""
|
||||
import requests
|
||||
attack_path = 'http://'
|
||||
if is_https:
|
||||
attack_path = 'https://'
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from logging import getLogger
|
||||
|
||||
from impacket.dcerpc.v5 import scmr, transport
|
||||
from impacket.smbconnection import SMB_DIALECT
|
||||
|
||||
from common.utils.attack_utils import ScanStatus, UsageEnum
|
||||
from common.utils.exploit_enum import ExploitType
|
||||
|
@ -104,7 +103,7 @@ class SmbExploiter(HostExploiter):
|
|||
LOG.debug("Exploiter SmbExec is giving up...")
|
||||
return False
|
||||
|
||||
self.set_vulnerable_port(self.host)
|
||||
self.set_vulnerable_port()
|
||||
# execute the remote dropper in case the path isn't final
|
||||
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
||||
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \
|
||||
|
@ -121,8 +120,7 @@ class SmbExploiter(HostExploiter):
|
|||
for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values():
|
||||
rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,))
|
||||
rpctransport.set_dport(port)
|
||||
if hasattr(rpctransport, 'preferred_dialect'):
|
||||
rpctransport.preferred_dialect(SMB_DIALECT)
|
||||
rpctransport.setRemoteHost(self.host.ip_addr)
|
||||
if hasattr(rpctransport, 'set_credentials'):
|
||||
# This method exists only for selected protocol sequences.
|
||||
rpctransport.set_credentials(user, password, '', lm_hash, ntlm_hash, None)
|
||||
|
@ -168,7 +166,7 @@ class SmbExploiter(HostExploiter):
|
|||
SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))
|
||||
return True
|
||||
|
||||
def set_vulnerable_port(self, host: VictimHost):
|
||||
def set_vulnerable_port(self):
|
||||
if 'tcp-445' in self.host.services:
|
||||
self.vulnerable_port = "445"
|
||||
elif 'tcp-139' in self.host.services:
|
||||
|
|
|
@ -56,7 +56,7 @@ class WebRCE(HostExploiter):
|
|||
Method that creates a dictionary of configuration values for exploit
|
||||
:return: configuration dict
|
||||
"""
|
||||
exploit_config = dict()
|
||||
exploit_config = {}
|
||||
|
||||
# dropper: If true monkey will use dropper parameter that will detach monkey's process and try to copy
|
||||
# it's file to the default destination path.
|
||||
|
|
|
@ -113,7 +113,7 @@ class NetworkScanner(object):
|
|||
:return: Victim or None if victim isn't alive
|
||||
"""
|
||||
LOG.debug("Scanning target address: %r", victim)
|
||||
if any([scanner.is_host_alive(victim) for scanner in self.scanners]):
|
||||
if any(scanner.is_host_alive(victim) for scanner in self.scanners):
|
||||
LOG.debug("Found potential target_ip: %r", victim)
|
||||
return victim
|
||||
else:
|
||||
|
|
|
@ -139,7 +139,7 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False):
|
|||
timeout = int(round(timeout)) # clamp to integer, to avoid checking input
|
||||
sockets_to_try = possible_ports[:]
|
||||
connected_ports_sockets = []
|
||||
while (timeout >= 0) and len(sockets_to_try):
|
||||
while (timeout >= 0) and sockets_to_try:
|
||||
sock_objects = [s[1] for s in sockets_to_try]
|
||||
|
||||
_, writeable_sockets, _ = select.select(sock_objects, sock_objects, sock_objects, 0)
|
||||
|
|
|
@ -67,8 +67,8 @@ def _get_windows_cred(pypykatz_cred: PypykatzCredential):
|
|||
|
||||
|
||||
def _hash_to_string(hash_: Any):
|
||||
if type(hash_) == str:
|
||||
if type(hash_) is str:
|
||||
return hash_
|
||||
if type(hash_) == bytes:
|
||||
if type(hash_) is bytes:
|
||||
return binascii.hexlify(bytearray(hash_)).decode()
|
||||
raise Exception(f"Can't convert hash_ to string, unsupported hash_ type {type(hash_)}")
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
from gevent import monkey as gevent_monkey
|
||||
|
||||
gevent_monkey.patch_all()
|
||||
|
||||
from monkey_island.cc.main import main
|
||||
|
||||
|
||||
|
@ -5,8 +9,9 @@ def parse_cli_args():
|
|||
import argparse
|
||||
parser = argparse.ArgumentParser(description="Infection Monkey Island CnC Server. See https://infectionmonkey.com")
|
||||
parser.add_argument("-s", "--setup-only", action="store_true",
|
||||
help="Pass this flag to cause the Island to setup and exit without actually starting. This is useful "
|
||||
"for preparing Island to boot faster later-on, so for compiling/packaging Islands.")
|
||||
help="Pass this flag to cause the Island to setup and exit without actually starting. "
|
||||
"This is useful for preparing Island to boot faster later-on, so for "
|
||||
"compiling/packaging Islands.")
|
||||
args = parser.parse_args()
|
||||
return args.setup_only
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import json
|
|||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from shutil import move
|
||||
|
||||
|
||||
def add_monkey_dir_to_sys_path():
|
||||
|
@ -16,6 +17,7 @@ add_monkey_dir_to_sys_path()
|
|||
from monkey_island.cc.environment.environment_config import EnvironmentConfig # noqa: E402 isort:skip
|
||||
|
||||
SERVER_CONFIG = "server_config"
|
||||
BACKUP_CONFIG_FILENAME = "./server_config.backup"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.addHandler(logging.StreamHandler())
|
||||
|
@ -26,10 +28,19 @@ def main():
|
|||
args = parse_args()
|
||||
file_path = EnvironmentConfig.get_config_file_path()
|
||||
|
||||
if args.server_config == "restore":
|
||||
restore_previous_config(file_path)
|
||||
quit()
|
||||
|
||||
# Read config
|
||||
with open(file_path) as config_file:
|
||||
config_data = json.load(config_file)
|
||||
|
||||
# Backup the config
|
||||
with open(BACKUP_CONFIG_FILENAME, "w") as backup_file:
|
||||
json.dump(config_data, backup_file, indent=4)
|
||||
backup_file.write("\n")
|
||||
|
||||
# Edit the config
|
||||
config_data[SERVER_CONFIG] = args.server_config
|
||||
|
||||
|
@ -42,10 +53,14 @@ def main():
|
|||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("server_config", choices=["standard", "testing", "password"])
|
||||
parser.add_argument("server_config", choices=["standard", "testing", "password", "restore"])
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
def restore_previous_config(config_path):
|
||||
move(BACKUP_CONFIG_FILENAME, config_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -6,6 +6,8 @@ from pathlib import Path
|
|||
from threading import Thread
|
||||
|
||||
# Add the monkey_island directory to the path, to make sure imports that don't start with "monkey_island." work.
|
||||
from gevent.pywsgi import WSGIServer
|
||||
|
||||
MONKEY_ISLAND_DIR_BASE_PATH = str(Path(__file__).parent.parent)
|
||||
if str(MONKEY_ISLAND_DIR_BASE_PATH) not in sys.path:
|
||||
sys.path.insert(0, MONKEY_ISLAND_DIR_BASE_PATH)
|
||||
|
@ -46,9 +48,6 @@ def main(should_setup_only=False):
|
|||
|
||||
|
||||
def start_island_server(should_setup_only):
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.wsgi import WSGIContainer
|
||||
|
||||
mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url())
|
||||
wait_for_mongo_db_server(mongo_url)
|
||||
|
@ -69,12 +68,11 @@ def start_island_server(should_setup_only):
|
|||
if env_singleton.env.is_debug():
|
||||
app.run(host='0.0.0.0', debug=True, ssl_context=(crt_path, key_path))
|
||||
else:
|
||||
http_server = HTTPServer(WSGIContainer(app),
|
||||
ssl_options={'certfile': os.environ.get('SERVER_CRT', crt_path),
|
||||
'keyfile': os.environ.get('SERVER_KEY', key_path)})
|
||||
http_server.listen(env_singleton.env.get_island_port())
|
||||
http_server = WSGIServer(('0.0.0.0', env_singleton.env.get_island_port()), app,
|
||||
certfile=os.environ.get('SERVER_CRT', crt_path),
|
||||
keyfile=os.environ.get('SERVER_KEY', key_path))
|
||||
log_init_info()
|
||||
IOLoop.instance().start()
|
||||
http_server.serve_forever()
|
||||
|
||||
|
||||
def log_init_info():
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import flask_restful
|
||||
from flask import send_from_directory
|
||||
|
||||
from monkey_island.cc.services.post_breach_files import UPLOADS_DIR
|
||||
from monkey_island.cc.services.post_breach_files import UPLOADS_DIR_NAME
|
||||
|
||||
__author__ = 'VakarisZ'
|
||||
|
||||
|
@ -13,4 +13,4 @@ class PBAFileDownload(flask_restful.Resource):
|
|||
|
||||
# Used by monkey. can't secure.
|
||||
def get(self, path):
|
||||
return send_from_directory(UPLOADS_DIR, path)
|
||||
return send_from_directory(UPLOADS_DIR_NAME, path)
|
||||
|
|
|
@ -12,6 +12,7 @@ EXPLOITER_CLASSES = {
|
|||
"SmbExploiter"
|
||||
],
|
||||
"title": "SMB Exploiter",
|
||||
"safe": True,
|
||||
"attack_techniques": ["T1110", "T1075", "T1035"],
|
||||
"info": "Brute forces using credentials provided by user and"
|
||||
" hashes gathered by mimikatz.",
|
||||
|
@ -23,6 +24,7 @@ EXPLOITER_CLASSES = {
|
|||
"WmiExploiter"
|
||||
],
|
||||
"title": "WMI Exploiter",
|
||||
"safe": True,
|
||||
"attack_techniques": ["T1110", "T1106"],
|
||||
"info": "Brute forces WMI (Windows Management Instrumentation) "
|
||||
"using credentials provided by user and hashes gathered by mimikatz.",
|
||||
|
@ -34,6 +36,7 @@ EXPLOITER_CLASSES = {
|
|||
"MSSQLExploiter"
|
||||
],
|
||||
"title": "MSSQL Exploiter",
|
||||
"safe": True,
|
||||
"attack_techniques": ["T1110"],
|
||||
"info": "Tries to brute force into MsSQL server and uses insecure "
|
||||
"configuration to execute commands on server.",
|
||||
|
@ -44,7 +47,8 @@ EXPLOITER_CLASSES = {
|
|||
"enum": [
|
||||
"Ms08_067_Exploiter"
|
||||
],
|
||||
"title": "MS08-067 Exploiter (UNSAFE)",
|
||||
"title": "MS08-067 Exploiter",
|
||||
"safe": False,
|
||||
"info": "Unsafe exploiter, that might cause system crash due to the use of buffer overflow. "
|
||||
"Uses MS08-067 vulnerability.",
|
||||
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/ms08-067/"
|
||||
|
@ -55,6 +59,7 @@ EXPLOITER_CLASSES = {
|
|||
"SSHExploiter"
|
||||
],
|
||||
"title": "SSH Exploiter",
|
||||
"safe": True,
|
||||
"attack_techniques": ["T1110", "T1145", "T1106"],
|
||||
"info": "Brute forces using credentials provided by user and SSH keys gathered from systems.",
|
||||
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sshexec/"
|
||||
|
@ -65,6 +70,7 @@ EXPLOITER_CLASSES = {
|
|||
"ShellShockExploiter"
|
||||
],
|
||||
"title": "ShellShock Exploiter",
|
||||
"safe": True,
|
||||
"info": "CVE-2014-6271, based on logic from "
|
||||
"https://github.com/nccgroup/shocker/blob/master/shocker.py .",
|
||||
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/shellshock/"
|
||||
|
@ -75,6 +81,7 @@ EXPLOITER_CLASSES = {
|
|||
"SambaCryExploiter"
|
||||
],
|
||||
"title": "SambaCry Exploiter",
|
||||
"safe": True,
|
||||
"info": "Bruteforces and searches for anonymous shares. Uses Impacket.",
|
||||
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sambacry/"
|
||||
},
|
||||
|
@ -84,6 +91,7 @@ EXPLOITER_CLASSES = {
|
|||
"ElasticGroovyExploiter"
|
||||
],
|
||||
"title": "ElasticGroovy Exploiter",
|
||||
"safe": True,
|
||||
"info": "CVE-2015-1427. Logic is based on Metasploit module.",
|
||||
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/elasticgroovy/"
|
||||
},
|
||||
|
@ -93,6 +101,7 @@ EXPLOITER_CLASSES = {
|
|||
"Struts2Exploiter"
|
||||
],
|
||||
"title": "Struts2 Exploiter",
|
||||
"safe": True,
|
||||
"info": "Exploits struts2 java web framework. CVE-2017-5638. Logic based on "
|
||||
"https://www.exploit-db.com/exploits/41570 .",
|
||||
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/struts2/"
|
||||
|
@ -103,6 +112,7 @@ EXPLOITER_CLASSES = {
|
|||
"WebLogicExploiter"
|
||||
],
|
||||
"title": "WebLogic Exploiter",
|
||||
"safe": True,
|
||||
"info": "Exploits CVE-2017-10271 and CVE-2019-2725 vulnerabilities on WebLogic server.",
|
||||
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/weblogic/"
|
||||
},
|
||||
|
@ -112,6 +122,7 @@ EXPLOITER_CLASSES = {
|
|||
"HadoopExploiter"
|
||||
],
|
||||
"title": "Hadoop/Yarn Exploiter",
|
||||
"safe": True,
|
||||
"info": "Remote code execution on HADOOP server with YARN and default settings. "
|
||||
"Logic based on https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn.",
|
||||
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/hadoop/"
|
||||
|
@ -122,6 +133,7 @@ EXPLOITER_CLASSES = {
|
|||
"VSFTPDExploiter"
|
||||
],
|
||||
"title": "VSFTPD Exploiter",
|
||||
"safe": True,
|
||||
"info": "Exploits a malicious backdoor that was added to the VSFTPD download archive. "
|
||||
"Logic based on Metasploit module.",
|
||||
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/vsftpd/"
|
||||
|
@ -132,6 +144,7 @@ EXPLOITER_CLASSES = {
|
|||
"DrupalExploiter"
|
||||
],
|
||||
"title": "Drupal Exploiter",
|
||||
"safe": True,
|
||||
"info": "Exploits a remote command execution vulnerability in a Drupal server,"
|
||||
"for which certain modules (such as RESTful Web Services) are enabled.",
|
||||
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/drupal/"
|
||||
|
|
|
@ -10,6 +10,7 @@ FINGER_CLASSES = {
|
|||
"SMBFinger"
|
||||
],
|
||||
"title": "SMBFinger",
|
||||
"safe": True,
|
||||
"info": "Figures out if SMB is running and what's the version of it.",
|
||||
"attack_techniques": ["T1210"]
|
||||
},
|
||||
|
@ -19,6 +20,7 @@ FINGER_CLASSES = {
|
|||
"SSHFinger"
|
||||
],
|
||||
"title": "SSHFinger",
|
||||
"safe": True,
|
||||
"info": "Figures out if SSH is running.",
|
||||
"attack_techniques": ["T1210"]
|
||||
},
|
||||
|
@ -28,6 +30,7 @@ FINGER_CLASSES = {
|
|||
"PingScanner"
|
||||
],
|
||||
"title": "PingScanner",
|
||||
"safe": True,
|
||||
"info": "Tries to identify if host is alive and which OS it's running by ping scan."
|
||||
},
|
||||
{
|
||||
|
@ -36,6 +39,7 @@ FINGER_CLASSES = {
|
|||
"HTTPFinger"
|
||||
],
|
||||
"title": "HTTPFinger",
|
||||
"safe": True,
|
||||
"info": "Checks if host has HTTP/HTTPS ports open."
|
||||
},
|
||||
{
|
||||
|
@ -44,6 +48,7 @@ FINGER_CLASSES = {
|
|||
"MySQLFinger"
|
||||
],
|
||||
"title": "MySQLFinger",
|
||||
"safe": True,
|
||||
"info": "Checks if MySQL server is running and tries to get it's version.",
|
||||
"attack_techniques": ["T1210"]
|
||||
},
|
||||
|
@ -53,6 +58,7 @@ FINGER_CLASSES = {
|
|||
"MSSQLFinger"
|
||||
],
|
||||
"title": "MSSQLFinger",
|
||||
"safe": True,
|
||||
"info": "Checks if Microsoft SQL service is running and tries to gather information about it.",
|
||||
"attack_techniques": ["T1210"]
|
||||
},
|
||||
|
@ -62,6 +68,7 @@ FINGER_CLASSES = {
|
|||
"ElasticFinger"
|
||||
],
|
||||
"title": "ElasticFinger",
|
||||
"safe": True,
|
||||
"info": "Checks if ElasticSearch is running and attempts to find it's version.",
|
||||
"attack_techniques": ["T1210"]
|
||||
},
|
||||
|
@ -71,6 +78,7 @@ FINGER_CLASSES = {
|
|||
"WindowsServerFinger"
|
||||
],
|
||||
"title": "WindowsServerFinger",
|
||||
"safe": True,
|
||||
"info": "Checks if server is a Windows Server and tests if it is vulnerable to Zerologon.",
|
||||
"attack_techniques": ["T1210"]
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ POST_BREACH_ACTIONS = {
|
|||
"BackdoorUser"
|
||||
],
|
||||
"title": "Back door user",
|
||||
"safe": True,
|
||||
"info": "Attempts to create a new user on the system and delete it afterwards.",
|
||||
"attack_techniques": ["T1136"]
|
||||
},
|
||||
|
@ -19,6 +20,7 @@ POST_BREACH_ACTIONS = {
|
|||
"CommunicateAsNewUser"
|
||||
],
|
||||
"title": "Communicate as new user",
|
||||
"safe": True,
|
||||
"info": "Attempts to create a new user, create HTTPS requests as that user and delete the user "
|
||||
"afterwards.",
|
||||
"attack_techniques": ["T1136"]
|
||||
|
@ -29,6 +31,7 @@ POST_BREACH_ACTIONS = {
|
|||
"ModifyShellStartupFiles"
|
||||
],
|
||||
"title": "Modify shell startup files",
|
||||
"safe": True,
|
||||
"info": "Attempts to modify shell startup files, like ~/.profile, ~/.bashrc, ~/.bash_profile "
|
||||
"in linux, and profile.ps1 in windows. Reverts modifications done afterwards.",
|
||||
"attack_techniques": ["T1156", "T1504"]
|
||||
|
@ -39,6 +42,7 @@ POST_BREACH_ACTIONS = {
|
|||
"HiddenFiles"
|
||||
],
|
||||
"title": "Hidden files and directories",
|
||||
"safe": True,
|
||||
"info": "Attempts to create a hidden file and remove it afterward.",
|
||||
"attack_techniques": ["T1158"]
|
||||
},
|
||||
|
@ -48,6 +52,7 @@ POST_BREACH_ACTIONS = {
|
|||
"TrapCommand"
|
||||
],
|
||||
"title": "Trap",
|
||||
"safe": True,
|
||||
"info": "On Linux systems, attempts to trap an interrupt signal in order to execute a command "
|
||||
"upon receiving that signal. Removes the trap afterwards.",
|
||||
"attack_techniques": ["T1154"]
|
||||
|
@ -58,6 +63,7 @@ POST_BREACH_ACTIONS = {
|
|||
"ChangeSetuidSetgid"
|
||||
],
|
||||
"title": "Setuid and Setgid",
|
||||
"safe": True,
|
||||
"info": "On Linux systems, attempts to set the setuid and setgid bits of a new file. "
|
||||
"Removes the file afterwards.",
|
||||
"attack_techniques": ["T1166"]
|
||||
|
@ -68,6 +74,7 @@ POST_BREACH_ACTIONS = {
|
|||
"ScheduleJobs"
|
||||
],
|
||||
"title": "Job scheduling",
|
||||
"safe": True,
|
||||
"info": "Attempts to create a scheduled job on the system and remove it.",
|
||||
"attack_techniques": ["T1168", "T1053"]
|
||||
},
|
||||
|
@ -77,6 +84,7 @@ POST_BREACH_ACTIONS = {
|
|||
"Timestomping"
|
||||
],
|
||||
"title": "Timestomping",
|
||||
"safe": True,
|
||||
"info": "Creates a temporary file and attempts to modify its time attributes. Removes the file afterwards.",
|
||||
"attack_techniques": ["T1099"]
|
||||
},
|
||||
|
@ -86,7 +94,8 @@ POST_BREACH_ACTIONS = {
|
|||
"SignedScriptProxyExecution"
|
||||
],
|
||||
"title": "Signed script proxy execution",
|
||||
"info": "On Windows systems, attemps to execute an arbitrary file "
|
||||
"safe": False,
|
||||
"info": "On Windows systems, attempts to execute an arbitrary file "
|
||||
"with the help of a pre-existing signed script.",
|
||||
"attack_techniques": ["T1216"]
|
||||
},
|
||||
|
@ -96,6 +105,7 @@ POST_BREACH_ACTIONS = {
|
|||
"AccountDiscovery"
|
||||
],
|
||||
"title": "Account Discovery",
|
||||
"safe": True,
|
||||
"info": "Attempts to get a listing of user accounts on the system.",
|
||||
"attack_techniques": ["T1087"]
|
||||
},
|
||||
|
@ -105,6 +115,7 @@ POST_BREACH_ACTIONS = {
|
|||
"ClearCommandHistory"
|
||||
],
|
||||
"title": "Clear command history",
|
||||
"safe": False,
|
||||
"info": "Attempts to clear the command history.",
|
||||
"attack_techniques": ["T1146"]
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
|
|||
ENVIRONMENT_COLLECTOR
|
||||
],
|
||||
"title": "Environment collector",
|
||||
"safe": True,
|
||||
"info": "Collects information about machine's environment (on premise/GCP/AWS).",
|
||||
"attack_techniques": ["T1082"]
|
||||
},
|
||||
|
@ -25,6 +26,7 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
|
|||
MIMIKATZ_COLLECTOR
|
||||
],
|
||||
"title": "Mimikatz collector",
|
||||
"safe": True,
|
||||
"info": "Collects credentials from Windows credential manager.",
|
||||
"attack_techniques": ["T1003", "T1005"]
|
||||
},
|
||||
|
@ -34,6 +36,7 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
|
|||
AWS_COLLECTOR
|
||||
],
|
||||
"title": "AWS collector",
|
||||
"safe": True,
|
||||
"info": "If on AWS, collects more information about the AWS instance currently running on.",
|
||||
"attack_techniques": ["T1082"]
|
||||
},
|
||||
|
@ -43,6 +46,7 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
|
|||
HOSTNAME_COLLECTOR
|
||||
],
|
||||
"title": "Hostname collector",
|
||||
"safe": True,
|
||||
"info": "Collects machine's hostname.",
|
||||
"attack_techniques": ["T1082", "T1016"]
|
||||
},
|
||||
|
@ -52,6 +56,7 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
|
|||
PROCESS_LIST_COLLECTOR
|
||||
],
|
||||
"title": "Process list collector",
|
||||
"safe": True,
|
||||
"info": "Collects a list of running processes on the machine.",
|
||||
"attack_techniques": ["T1082"]
|
||||
},
|
||||
|
@ -61,6 +66,7 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
|
|||
AZURE_CRED_COLLECTOR
|
||||
],
|
||||
"title": "Azure credential collector",
|
||||
"safe": True,
|
||||
"info": "Collects password credentials from Azure VMs",
|
||||
"attack_techniques": ["T1003", "T1005"]
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ logger = logging.getLogger(__name__)
|
|||
# Where to find file names in config
|
||||
PBA_WINDOWS_FILENAME_PATH = ['monkey', 'post_breach', 'PBA_windows_filename']
|
||||
PBA_LINUX_FILENAME_PATH = ['monkey', 'post_breach', 'PBA_linux_filename']
|
||||
PBA_UPLOAD_PATH = ['monkey_island', 'cc', 'userUploads']
|
||||
UPLOADS_DIR_NAME = 'userUploads'
|
||||
PBA_UPLOAD_PATH = ['monkey_island', 'cc', UPLOADS_DIR_NAME]
|
||||
UPLOADS_DIR = Path(*PBA_UPLOAD_PATH)
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import logging
|
||||
import threading
|
||||
|
||||
from gevent.lock import BoundedSemaphore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -7,9 +8,9 @@ logger = logging.getLogger(__name__)
|
|||
# Report generation can be quite slow if there is a lot of data, and the UI queries the Root service often; without
|
||||
# the locks, these requests would accumulate, overload the server, eventually causing it to crash.
|
||||
logger.debug("Initializing report generation locks.")
|
||||
__report_generating_lock = threading.Semaphore()
|
||||
__attack_report_generating_lock = threading.Semaphore()
|
||||
__regular_report_generating_lock = threading.Semaphore()
|
||||
__report_generating_lock = BoundedSemaphore()
|
||||
__attack_report_generating_lock = BoundedSemaphore()
|
||||
__regular_report_generating_lock = BoundedSemaphore()
|
||||
|
||||
|
||||
def safe_generate_reports():
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from monkey_island.cc.database import mongo
|
||||
from monkey_island.cc.models import Monkey
|
||||
from monkey_island.cc.services.edge.edge import EdgeService
|
||||
from monkey_island.cc.services.node import NodeService
|
||||
from monkey_island.cc.services.telemetry.processing.utils import \
|
||||
get_edge_by_scan_or_exploit_telemetry
|
||||
|
|
|
@ -35,7 +35,8 @@
|
|||
"comma-dangle": 1,
|
||||
"quotes": [
|
||||
1,
|
||||
"single"
|
||||
"single",
|
||||
{"allowTemplateLiterals": true}
|
||||
],
|
||||
"no-undef": 1,
|
||||
"global-strict": 0,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React, {useEffect, useState} from 'react';
|
||||
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
|
||||
import DropdownSelect from '../../ui-components/DropdownSelect';
|
||||
import {OS_TYPES} from './OsTypes';
|
||||
import GenerateLocalWindowsCmd from './commands/local_windows_cmd';
|
||||
import GenerateLocalWindowsPowershell from './commands/local_windows_powershell';
|
||||
import GenerateLocalLinuxWget from './commands/local_linux_wget';
|
||||
import GenerateLocalLinuxCurl from './commands/local_linux_curl';
|
||||
import CommandDisplay from './CommandDisplay';
|
||||
import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
|
||||
import DropdownSelect from '../../../ui-components/DropdownSelect';
|
||||
import {OS_TYPES} from '../utils/OsTypes';
|
||||
import GenerateLocalWindowsCmd from '../commands/local_windows_cmd';
|
||||
import GenerateLocalWindowsPowershell from '../commands/local_windows_powershell';
|
||||
import GenerateLocalLinuxWget from '../commands/local_linux_wget';
|
||||
import GenerateLocalLinuxCurl from '../commands/local_linux_curl';
|
||||
import CommandDisplay from '../utils/CommandDisplay';
|
||||
|
||||
|
||||
const LocalManualRunOptions = (props) => {
|
|
@ -0,0 +1,119 @@
|
|||
import React, {useState} from 'react';
|
||||
import ReactTable from 'react-table'
|
||||
import checkboxHOC from 'react-table/lib/hoc/selectTable';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
||||
const CheckboxTable = checkboxHOC(ReactTable);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
Header: 'Machines',
|
||||
columns: [
|
||||
{Header: 'Machine', accessor: 'name'},
|
||||
{Header: 'Instance ID', accessor: 'instance_id'},
|
||||
{Header: 'IP Address', accessor: 'ip_address'},
|
||||
{Header: 'OS', accessor: 'os'}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const pageSize = 10;
|
||||
|
||||
function AWSInstanceTable(props) {
|
||||
|
||||
const [allToggled, setAllToggled] = useState(false);
|
||||
let checkboxTable = null;
|
||||
|
||||
function toggleSelection(key) {
|
||||
key = key.replace('select-', '');
|
||||
// start off with the existing state
|
||||
let modifiedSelection = [...props.selection];
|
||||
const keyIndex = modifiedSelection.indexOf(key);
|
||||
// check to see if the key exists
|
||||
if (keyIndex >= 0) {
|
||||
// it does exist so we will remove it using destructing
|
||||
modifiedSelection = [
|
||||
...modifiedSelection.slice(0, keyIndex),
|
||||
...modifiedSelection.slice(keyIndex + 1)
|
||||
];
|
||||
} else {
|
||||
// it does not exist so add it
|
||||
modifiedSelection.push(key);
|
||||
}
|
||||
// update the state
|
||||
props.setSelection(modifiedSelection);
|
||||
}
|
||||
|
||||
function isSelected(key) {
|
||||
return props.selection.includes(key);
|
||||
}
|
||||
|
||||
function toggleAll() {
|
||||
const selectAll = !allToggled;
|
||||
const selection = [];
|
||||
if (selectAll) {
|
||||
// we need to get at the internals of ReactTable
|
||||
const wrappedInstance = checkboxTable.getWrappedInstance();
|
||||
// the 'sortedData' property contains the currently accessible records based on the filter and sort
|
||||
const currentRecords = wrappedInstance.getResolvedState().sortedData;
|
||||
// we just push all the IDs onto the selection array
|
||||
currentRecords.forEach(item => {
|
||||
selection.push(item._original.instance_id);
|
||||
});
|
||||
}
|
||||
setAllToggled(selectAll);
|
||||
props.setSelection(selection);
|
||||
}
|
||||
|
||||
function getTrProps(_, r) {
|
||||
let color = 'inherit';
|
||||
if (r) {
|
||||
let instId = r.original.instance_id;
|
||||
if (isSelected(instId)) {
|
||||
color = '#ffed9f';
|
||||
} else if (Object.prototype.hasOwnProperty.call(props.results, instId)) {
|
||||
color = props.results[instId] ? '#00f01b' : '#f00000'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
style: {backgroundColor: color}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="data-table-container">
|
||||
<CheckboxTable
|
||||
ref={r => (checkboxTable = r)}
|
||||
keyField="instance_id"
|
||||
columns={columns}
|
||||
data={props.data}
|
||||
showPagination={true}
|
||||
defaultPageSize={pageSize}
|
||||
className="-highlight"
|
||||
selectType="checkbox"
|
||||
toggleSelection={toggleSelection}
|
||||
isSelected={isSelected}
|
||||
toggleAll={toggleAll}
|
||||
selectAll={allToggled}
|
||||
getTrProps={getTrProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
AWSInstanceTable.propTypes = {
|
||||
data: PropTypes.arrayOf(PropTypes.exact({
|
||||
instance_id: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
os: PropTypes.string,
|
||||
ip_address: PropTypes.string
|
||||
})),
|
||||
results: PropTypes.arrayOf(PropTypes.string),
|
||||
selection: PropTypes.arrayOf(PropTypes.string),
|
||||
setSelection: PropTypes.func
|
||||
}
|
||||
|
||||
export default AWSInstanceTable;
|
|
@ -0,0 +1,80 @@
|
|||
import React, {useEffect, useState} from 'react';
|
||||
import AuthComponent from '../../../AuthComponent';
|
||||
import '../../../../styles/components/RunOnIslandButton.scss';
|
||||
import {faCloud} from '@fortawesome/free-solid-svg-icons';
|
||||
import AWSRunOptions from './AWSRunOptions';
|
||||
import NextSelectionButton from '../../../ui-components/inline-selection/NextSelectionButton';
|
||||
import {Alert, Button} from 'react-bootstrap';
|
||||
import LoadingIcon from '../../../ui-components/LoadingIcon';
|
||||
|
||||
|
||||
function AWSRunButton(props) {
|
||||
|
||||
const authComponent = new AuthComponent({});
|
||||
|
||||
const [isOnAWS, setIsOnAWS] = useState(false);
|
||||
const [AWSInstances, setAWSInstances] = useState([]);
|
||||
const [awsMachineCollectionError, setAwsMachineCollectionError] = useState('');
|
||||
const [componentLoading, setComponentLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
checkIsOnAWS();
|
||||
}, []);
|
||||
|
||||
function checkIsOnAWS() {
|
||||
authComponent.authFetch('/api/remote-monkey?action=list_aws')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
let isAws = res['is_aws'];
|
||||
setComponentLoading(false);
|
||||
if (isAws) {
|
||||
// On AWS!
|
||||
// Checks if there was an error while collecting the aws machines.
|
||||
let isErrorWhileCollectingAwsMachines = (res['error'] != null);
|
||||
if (isErrorWhileCollectingAwsMachines) {
|
||||
// There was an error. Finish loading, and display error message.
|
||||
setIsOnAWS(true);
|
||||
setAwsMachineCollectionError(res['error']);
|
||||
} else {
|
||||
// No error! Finish loading and display machines for user
|
||||
setIsOnAWS(true);
|
||||
setAWSInstances(res['instances']);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getAWSButton() {
|
||||
return <NextSelectionButton title={'AWS run'}
|
||||
description={'Run on a chosen AWS instance in the cloud.'}
|
||||
icon={faCloud}
|
||||
onButtonClick={() => {
|
||||
props.setComponent(AWSRunOptions,
|
||||
{AWSInstances: AWSInstances, setComponent: props.setComponent})
|
||||
}}/>
|
||||
}
|
||||
|
||||
function getErrorDisplay() {
|
||||
return (
|
||||
<Alert variant={'info'}>Detected ability to run on different AWS instances.
|
||||
To enable this feature, follow the
|
||||
<Button variant={'link'} className={'inline-link'}
|
||||
href={'https://www.guardicore.com/infectionmonkey/docs/usage/integrations/aws-run-on-ec2-machine/'}>
|
||||
Tutorial
|
||||
</Button> and refresh the page. Error received while trying to list AWS instances: {awsMachineCollectionError}
|
||||
</Alert> );
|
||||
}
|
||||
|
||||
let displayed = '';
|
||||
if (componentLoading) {
|
||||
displayed = LoadingIcon();
|
||||
}
|
||||
if (awsMachineCollectionError !== '') {
|
||||
displayed = getErrorDisplay();
|
||||
} else if (isOnAWS) {
|
||||
displayed = getAWSButton();
|
||||
}
|
||||
return displayed;
|
||||
}
|
||||
|
||||
export default AWSRunButton;
|
|
@ -0,0 +1,117 @@
|
|||
import React, {useEffect, useState} from 'react';
|
||||
import {Button, Nav} from 'react-bootstrap';
|
||||
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faSync} from '@fortawesome/free-solid-svg-icons/faSync';
|
||||
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
|
||||
import AwsRunTable from './AWSInstanceTable';
|
||||
import AuthComponent from '../../../AuthComponent';
|
||||
import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
|
||||
|
||||
|
||||
const AWSRunOptions = (props) => {
|
||||
return InlineSelection(getContents, {
|
||||
...props,
|
||||
onBackButtonClick: () => {props.setComponent()}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const getContents = (props) => {
|
||||
|
||||
const authComponent = new AuthComponent({});
|
||||
|
||||
let [allIPs, setAllIPs] = useState([]);
|
||||
let [selectedIp, setSelectedIp] = useState(null);
|
||||
let [AWSClicked, setAWSClicked] = useState(false);
|
||||
let [runResults, setRunResults] = useState([]);
|
||||
let [selectedInstances, setSelectedInstances] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
getIps();
|
||||
}, []);
|
||||
|
||||
function getIps() {
|
||||
authComponent.authFetch('/api')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
setAllIPs(res['ip_addresses']);
|
||||
setSelectedIp(res['ip_addresses'][0]);
|
||||
});
|
||||
}
|
||||
|
||||
function runOnAws() {
|
||||
setAWSClicked(true);
|
||||
let instances = selectedInstances.map(x => instanceIdToInstance(x));
|
||||
|
||||
authComponent.authFetch('/api/remote-monkey',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({type: 'aws', instances: instances, island_ip: selectedIp})
|
||||
}).then(res => res.json())
|
||||
.then(res => {
|
||||
let result = res['result'];
|
||||
|
||||
// update existing state, not run-over
|
||||
let prevRes = result;
|
||||
for (let key in result) {
|
||||
if (result.hasOwnProperty(key)) {
|
||||
prevRes[key] = result[key];
|
||||
}
|
||||
}
|
||||
setRunResults(prevRes);
|
||||
setSelectedInstances([]);
|
||||
setAWSClicked(false);
|
||||
});
|
||||
}
|
||||
|
||||
function instanceIdToInstance(instance_id) {
|
||||
let instance = props.AWSInstances.find(
|
||||
function (inst) {
|
||||
return inst['instance_id'] === instance_id;
|
||||
});
|
||||
|
||||
return {'instance_id': instance_id, 'os': instance['os']}
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{'marginBottom': '2em'}}>
|
||||
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
|
||||
<p className="alert alert-info">
|
||||
<FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}}/>
|
||||
Not sure what this is? Not seeing your AWS EC2 instances? <a
|
||||
href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances"
|
||||
rel="noopener noreferrer" target="_blank">Read the documentation</a>!
|
||||
</p>
|
||||
</div>
|
||||
{
|
||||
allIPs.length > 1 ?
|
||||
<Nav variant="pills" activeKey={selectedIp} onSelect={setSelectedIp}
|
||||
style={{'marginBottom': '2em'}}>
|
||||
{allIPs.map(ip => <Nav.Item key={ip}><Nav.Link eventKey={ip}>{ip}</Nav.Link></Nav.Item>)}
|
||||
</Nav>
|
||||
: <div style={{'marginBottom': '2em'}}/>
|
||||
}
|
||||
<AwsRunTable
|
||||
data={props.AWSInstances}
|
||||
results={runResults}
|
||||
selection={selectedInstances}
|
||||
setSelection={setSelectedInstances}
|
||||
/>
|
||||
<div className={'aws-run-button-container'}>
|
||||
<Button
|
||||
size={'lg'}
|
||||
onClick={runOnAws}
|
||||
className={'btn btn-default btn-md center-block'}
|
||||
disabled={AWSClicked}>
|
||||
Run on selected machines
|
||||
{AWSClicked ?
|
||||
<FontAwesomeIcon icon={faSync} className={`text-success spinning-icon`} style={{'marginLeft': '5px'}}/> : null}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AWSRunOptions;
|
|
@ -1,12 +1,13 @@
|
|||
import React, {useEffect, useState} from 'react';
|
||||
import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton';
|
||||
import LocalManualRunOptions from './LocalManualRunOptions';
|
||||
import LocalManualRunOptions from './RunManually/LocalManualRunOptions';
|
||||
import AuthComponent from '../../AuthComponent';
|
||||
import {faLaptopCode} from '@fortawesome/free-solid-svg-icons/faLaptopCode';
|
||||
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
|
||||
import {cloneDeep} from 'lodash';
|
||||
import {faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons';
|
||||
import RunOnIslandButton from './RunOnIslandButton';
|
||||
import AWSRunButton from './RunOnAWS/AWSRunButton';
|
||||
|
||||
function RunOptions(props) {
|
||||
|
||||
|
@ -61,6 +62,7 @@ function RunOptions(props) {
|
|||
setComponent(LocalManualRunOptions,
|
||||
{ips: ips, setComponent: setComponent})
|
||||
}}/>
|
||||
<AWSRunButton setComponent={setComponent}/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {OS_TYPES} from '../OsTypes';
|
||||
import {OS_TYPES} from '../utils/OsTypes';
|
||||
|
||||
|
||||
export default function generateLocalLinuxCurl(ip, osType) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {OS_TYPES} from '../OsTypes';
|
||||
import {OS_TYPES} from '../utils/OsTypes';
|
||||
|
||||
|
||||
export default function generateLocalLinuxWget(ip, osType) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {OS_TYPES} from '../OsTypes';
|
||||
import {OS_TYPES} from '../utils/OsTypes';
|
||||
|
||||
|
||||
export default function generateLocalWindowsCmd(ip, osType) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {OS_TYPES} from '../OsTypes';
|
||||
import {OS_TYPES} from '../utils/OsTypes';
|
||||
|
||||
|
||||
export default function generateLocalWindowsPowershell(ip, osType) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
|
||||
import LocalManualRunOptions from './LocalManualRunOptions';
|
||||
import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
|
||||
import LocalManualRunOptions from '../RunManually/LocalManualRunOptions';
|
||||
|
||||
function InterfaceSelection(props) {
|
||||
return InlineSelection(getContents, props, LocalManualRunOptions)
|
|
@ -1,109 +0,0 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table'
|
||||
import checkboxHOC from 'react-table/lib/hoc/selectTable';
|
||||
|
||||
const CheckboxTable = checkboxHOC(ReactTable);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
Header: 'Machines',
|
||||
columns: [
|
||||
{Header: 'Machine', accessor: 'name'},
|
||||
{Header: 'Instance ID', accessor: 'instance_id'},
|
||||
{Header: 'IP Address', accessor: 'ip_address'},
|
||||
{Header: 'OS', accessor: 'os'}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const pageSize = 10;
|
||||
|
||||
class AwsRunTableComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selection: [],
|
||||
selectAll: false,
|
||||
result: {}
|
||||
}
|
||||
}
|
||||
|
||||
toggleSelection = (key) => {
|
||||
// start off with the existing state
|
||||
let selection = [...this.state.selection];
|
||||
const keyIndex = selection.indexOf(key);
|
||||
// check to see if the key exists
|
||||
if (keyIndex >= 0) {
|
||||
// it does exist so we will remove it using destructing
|
||||
selection = [
|
||||
...selection.slice(0, keyIndex),
|
||||
...selection.slice(keyIndex + 1)
|
||||
];
|
||||
} else {
|
||||
// it does not exist so add it
|
||||
selection.push(key);
|
||||
}
|
||||
// update the state
|
||||
this.setState({selection});
|
||||
};
|
||||
|
||||
isSelected = key => {
|
||||
return this.state.selection.includes(key);
|
||||
};
|
||||
|
||||
toggleAll = () => {
|
||||
const selectAll = !this.state.selectAll;
|
||||
const selection = [];
|
||||
if (selectAll) {
|
||||
// we need to get at the internals of ReactTable
|
||||
const wrappedInstance = this.checkboxTable.getWrappedInstance();
|
||||
// the 'sortedData' property contains the currently accessible records based on the filter and sort
|
||||
const currentRecords = wrappedInstance.getResolvedState().sortedData;
|
||||
// we just push all the IDs onto the selection array
|
||||
currentRecords.forEach(item => {
|
||||
selection.push(item._original.instance_id);
|
||||
});
|
||||
}
|
||||
this.setState({selectAll, selection});
|
||||
};
|
||||
|
||||
getTrProps = (_, r) => {
|
||||
let color = 'inherit';
|
||||
if (r) {
|
||||
let instId = r.original.instance_id;
|
||||
if (this.isSelected(instId)) {
|
||||
color = '#ffed9f';
|
||||
} else if (Object.prototype.hasOwnProperty.call(this.state.result, instId)) {
|
||||
color = this.state.result[instId] ? '#00f01b' : '#f00000'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
style: {backgroundColor: color}
|
||||
};
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="data-table-container">
|
||||
<CheckboxTable
|
||||
ref={r => (this.checkboxTable = r)}
|
||||
keyField="instance_id"
|
||||
columns={columns}
|
||||
data={this.props.data}
|
||||
showPagination={true}
|
||||
defaultPageSize={pageSize}
|
||||
className="-highlight"
|
||||
selectType="checkbox"
|
||||
toggleSelection={this.toggleSelection}
|
||||
isSelected={this.isSelected}
|
||||
toggleAll={this.toggleAll}
|
||||
selectAll={this.state.selectAll}
|
||||
getTrProps={this.getTrProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AwsRunTableComponent;
|
|
@ -1,121 +1,170 @@
|
|||
import React, {useState} from 'react';
|
||||
import React from 'react';
|
||||
import {Button, Card} from 'react-bootstrap';
|
||||
|
||||
import {Card, Button, Form} from 'react-bootstrap';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faCheckSquare} from '@fortawesome/free-solid-svg-icons';
|
||||
import {faSquare} from '@fortawesome/free-regular-svg-icons';
|
||||
import {cloneDeep} from 'lodash';
|
||||
|
||||
import {getComponentHeight} from './utils/HeightCalculator';
|
||||
import {resolveObjectPath} from './utils/ObjectPathResolver';
|
||||
import InfoPane from './InfoPane';
|
||||
import {getDefaultPaneParams, InfoPane} from './InfoPane';
|
||||
import {MasterCheckbox, MasterCheckboxState} from './MasterCheckbox';
|
||||
import ChildCheckboxContainer from './ChildCheckbox';
|
||||
import {getFullDefinitionByKey} from './JsonSchemaHelpers';
|
||||
|
||||
function AdvancedMultiSelectHeader(props) {
|
||||
const {
|
||||
title,
|
||||
disabled,
|
||||
onCheckboxClick,
|
||||
checkboxState,
|
||||
hideReset,
|
||||
onResetClick
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Card.Header className="d-flex justify-content-between">
|
||||
<MasterCheckbox title={title} disabled={disabled} onClick={onCheckboxClick} checkboxState={checkboxState}/>
|
||||
<Button className={'reset-safe-defaults'} type={'reset'} variant={'warning'}
|
||||
hidden={hideReset} onClick={onResetClick}>
|
||||
Reset to safe defaults
|
||||
</Button>
|
||||
</Card.Header>
|
||||
);
|
||||
}
|
||||
|
||||
class AdvancedMultiSelect extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.enumOptions = props.options.enumOptions;
|
||||
this.defaultValues = props.schema.default;
|
||||
this.infoPaneRefString = props.schema.items.$ref;
|
||||
this.registry = props.registry;
|
||||
|
||||
this.state = {
|
||||
masterCheckboxState: this.getMasterCheckboxState(props.value),
|
||||
hideReset: this.getHideResetState(props.value),
|
||||
infoPaneParams: getDefaultPaneParams(this.infoPaneRefString, this.registry)
|
||||
};
|
||||
}
|
||||
|
||||
onMasterCheckboxClick = () => {
|
||||
if (this.state.masterCheckboxState === MasterCheckboxState.ALL) {
|
||||
var newValues = [];
|
||||
} else {
|
||||
newValues = this.enumOptions.map(({value}) => value);
|
||||
}
|
||||
|
||||
this.props.onChange(newValues);
|
||||
this.setMasterCheckboxState(newValues);
|
||||
this.setHideResetState(newValues);
|
||||
}
|
||||
|
||||
onChildCheckboxClick = (value) => {
|
||||
let selectValues = this.getSelectValuesAfterClick(value);
|
||||
this.props.onChange(selectValues);
|
||||
|
||||
this.setMasterCheckboxState(selectValues);
|
||||
this.setHideResetState(selectValues);
|
||||
}
|
||||
|
||||
getSelectValuesAfterClick(clickedValue) {
|
||||
const valueArray = cloneDeep(this.props.value);
|
||||
|
||||
function getSelectValuesAfterClick(valueArray, clickedValue) {
|
||||
if (valueArray.includes(clickedValue)) {
|
||||
return valueArray.filter((e) => {
|
||||
return e !== clickedValue;
|
||||
});
|
||||
return valueArray.filter(e => e !== clickedValue);
|
||||
} else {
|
||||
valueArray.push(clickedValue);
|
||||
return valueArray;
|
||||
}
|
||||
}
|
||||
|
||||
function onMasterCheckboxClick(checkboxValue, defaultArray, onChangeFnc) {
|
||||
if (checkboxValue) {
|
||||
onChangeFnc([]);
|
||||
} else {
|
||||
onChangeFnc(defaultArray);
|
||||
}
|
||||
}
|
||||
|
||||
// Definitions passed to components only contains value and label,
|
||||
// custom fields like "info" or "links" must be pulled from registry object using this function
|
||||
function getFullDefinitionsFromRegistry(refString, registry) {
|
||||
return getObjectFromRegistryByRef(refString, registry).anyOf;
|
||||
}
|
||||
setMasterCheckboxState(selectValues) {
|
||||
this.setState(() => ({
|
||||
masterCheckboxState: this.getMasterCheckboxState(selectValues)
|
||||
}));
|
||||
}
|
||||
|
||||
function getObjectFromRegistryByRef(refString, registry) {
|
||||
let refArray = refString.replace('#', '').split('/');
|
||||
return resolveObjectPath(refArray, registry);
|
||||
}
|
||||
getMasterCheckboxState(selectValues) {
|
||||
if (selectValues.length === 0) {
|
||||
return MasterCheckboxState.NONE;
|
||||
}
|
||||
|
||||
function getFullDefinitionByKey(refString, registry, itemKey) {
|
||||
let fullArray = getFullDefinitionsFromRegistry(refString, registry);
|
||||
return fullArray.filter(e => (e.enum[0] === itemKey))[0];
|
||||
}
|
||||
if (selectValues.length != this.enumOptions.length) {
|
||||
return MasterCheckboxState.MIXED;
|
||||
}
|
||||
|
||||
function setPaneInfo(refString, registry, itemKey, setPaneInfoFnc) {
|
||||
let definitionObj = getFullDefinitionByKey(refString, registry, itemKey);
|
||||
setPaneInfoFnc({title: definitionObj.title, content: definitionObj.info, link: definitionObj.link});
|
||||
}
|
||||
return MasterCheckboxState.ALL;
|
||||
}
|
||||
|
||||
function getDefaultPaneParams(refString, registry) {
|
||||
let configSection = getObjectFromRegistryByRef(refString, registry);
|
||||
return ({title: configSection.title, content: configSection.description});
|
||||
}
|
||||
onResetClick = () => {
|
||||
this.props.onChange(this.defaultValues);
|
||||
this.setHideResetState(this.defaultValues);
|
||||
this.setMasterCheckboxState(this.defaultValues);
|
||||
this.setPaneInfoToDefault();
|
||||
}
|
||||
|
||||
function AdvancedMultiSelect(props) {
|
||||
const [masterCheckbox, setMasterCheckbox] = useState(true);
|
||||
setHideResetState(selectValues) {
|
||||
this.setState(() => ({
|
||||
hideReset: this.getHideResetState(selectValues)
|
||||
}));
|
||||
}
|
||||
|
||||
getHideResetState(selectValues) {
|
||||
return selectValues.every((value) => this.defaultValues.includes(value));
|
||||
}
|
||||
|
||||
setPaneInfo = (itemKey) => {
|
||||
let definitionObj = getFullDefinitionByKey(this.infoPaneRefString, this.registry, itemKey);
|
||||
this.setState(
|
||||
{
|
||||
infoPaneParams: {
|
||||
title: definitionObj.title,
|
||||
content: definitionObj.info,
|
||||
link: definitionObj.link,
|
||||
showWarning: !(this.isSafe(itemKey))
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
setPaneInfoToDefault() {
|
||||
this.setState(() => ({
|
||||
infoPaneParams: getDefaultPaneParams(this.props.schema.items.$ref, this.props.registry)
|
||||
}));
|
||||
}
|
||||
|
||||
isSafe = (itemKey) => {
|
||||
return getFullDefinitionByKey(this.infoPaneRefString, this.registry, itemKey).safe;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
schema,
|
||||
id,
|
||||
options,
|
||||
value,
|
||||
required,
|
||||
disabled,
|
||||
readonly,
|
||||
multiple,
|
||||
autofocus,
|
||||
onChange,
|
||||
registry
|
||||
} = props;
|
||||
const {enumOptions} = options;
|
||||
const [infoPaneParams, setInfoPaneParams] = useState(getDefaultPaneParams(schema.items.$ref, registry));
|
||||
getDefaultPaneParams(schema.items.$ref, registry);
|
||||
const selectValue = cloneDeep(value);
|
||||
autofocus
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className={'advanced-multi-select'}>
|
||||
<Card.Header>
|
||||
<Button key={`${props.schema.title}-button`} value={value}
|
||||
variant={'link'} disabled={disabled}
|
||||
onClick={() => {
|
||||
onMasterCheckboxClick(masterCheckbox, schema.default, onChange);
|
||||
setMasterCheckbox(!masterCheckbox);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={masterCheckbox ? faCheckSquare : faSquare}/>
|
||||
</Button>
|
||||
<span className={'header-title'}>{props.schema.title}</span>
|
||||
</Card.Header>
|
||||
<Form.Group
|
||||
style={{height: `${getComponentHeight(enumOptions.length)}px`}}
|
||||
id={id}
|
||||
multiple={multiple}
|
||||
className='choice-block form-control'
|
||||
required={required}
|
||||
disabled={disabled || readonly}
|
||||
autoFocus={autofocus}>
|
||||
{enumOptions.map(({value, label}, i) => {
|
||||
return (
|
||||
<Form.Group
|
||||
key={i}
|
||||
onClick={() => setPaneInfo(schema.items.$ref, registry, value, setInfoPaneParams)}>
|
||||
<Button value={value} variant={'link'} disabled={disabled}
|
||||
onClick={() => onChange(getSelectValuesAfterClick(selectValue, value))}>
|
||||
<FontAwesomeIcon icon={selectValue.includes(value) ? faCheckSquare : faSquare}/>
|
||||
</Button>
|
||||
<span className={'option-text'}>
|
||||
{label}
|
||||
</span>
|
||||
</Form.Group>
|
||||
);
|
||||
})}
|
||||
</Form.Group>
|
||||
<InfoPane title={infoPaneParams.title} body={infoPaneParams.content} link={infoPaneParams.link}/>
|
||||
<AdvancedMultiSelectHeader title={schema.title}
|
||||
disabled={disabled} onCheckboxClick={this.onMasterCheckboxClick}
|
||||
checkboxState={this.state.masterCheckboxState}
|
||||
hideReset={this.state.hideReset} onResetClick={this.onResetClick}/>
|
||||
|
||||
<ChildCheckboxContainer id={id} multiple={multiple} required={required}
|
||||
disabled={disabled || readonly} autoFocus={autofocus} isSafe={this.isSafe}
|
||||
onPaneClick={this.setPaneInfo} onCheckboxClick={this.onChildCheckboxClick}
|
||||
selectedValues={this.props.value} enumOptions={this.enumOptions}/>
|
||||
|
||||
<InfoPane title={this.state.infoPaneParams.title}
|
||||
body={this.state.infoPaneParams.content}
|
||||
link={this.state.infoPaneParams.link}
|
||||
showWarning={this.state.infoPaneParams.showWarning}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AdvancedMultiSelect;
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
import React from 'react';
|
||||
import {Button, Form} from 'react-bootstrap';
|
||||
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faCheckSquare} from '@fortawesome/free-solid-svg-icons';
|
||||
import {faSquare} from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
import {getComponentHeight} from './utils/HeightCalculator';
|
||||
import WarningIcon from './WarningIcon';
|
||||
|
||||
function ChildCheckboxContainer(props) {
|
||||
const {
|
||||
enumOptions,
|
||||
id,
|
||||
multiple,
|
||||
required,
|
||||
disabled,
|
||||
autofocus,
|
||||
onPaneClick,
|
||||
onCheckboxClick,
|
||||
selectedValues,
|
||||
isSafe
|
||||
} = props;
|
||||
|
||||
return(
|
||||
<Form.Group
|
||||
style={{height: `${getComponentHeight(enumOptions.length)}px`}}
|
||||
id={id} multiple={multiple} className='choice-block form-control'
|
||||
required={required} disabled={disabled} autoFocus={autofocus}>
|
||||
{
|
||||
enumOptions.map(({value, label}, i) => {
|
||||
return (
|
||||
<ChildCheckbox key={i} onPaneClick={onPaneClick}
|
||||
onClick={onCheckboxClick} value={value}
|
||||
disabled={disabled} label={label} checkboxState={selectedValues.includes(value)}
|
||||
safe={isSafe(value)}/>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
function ChildCheckbox(props) {
|
||||
const {
|
||||
onPaneClick,
|
||||
onClick,
|
||||
value,
|
||||
disabled,
|
||||
label,
|
||||
checkboxState,
|
||||
safe
|
||||
} = props;
|
||||
|
||||
let displayLabel = [<span key={'label'} className={'option-text'}>{label}</span>];
|
||||
|
||||
if (!safe) {
|
||||
displayLabel.push(<WarningIcon key="warning-icon"/>)
|
||||
}
|
||||
|
||||
return (
|
||||
<Form.Group onClick={() => onPaneClick(value)}>
|
||||
<Button value={value} variant={'link'} disabled={disabled} onClick={() => onClick(value)}>
|
||||
<FontAwesomeIcon icon={checkboxState ? faCheckSquare : faSquare}/>
|
||||
</Button>
|
||||
{displayLabel}
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChildCheckboxContainer;
|
|
@ -3,6 +3,18 @@ import React from 'react';
|
|||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import {getObjectFromRegistryByRef} from './JsonSchemaHelpers';
|
||||
import WarningIcon from './WarningIcon';
|
||||
|
||||
function getDefaultPaneParams(refString, registry) {
|
||||
let configSection = getObjectFromRegistryByRef(refString, registry);
|
||||
return (
|
||||
{
|
||||
title: configSection.title,
|
||||
content: configSection.description,
|
||||
showWarning: false
|
||||
});
|
||||
}
|
||||
|
||||
function InfoPane(props) {
|
||||
return (
|
||||
|
@ -42,11 +54,27 @@ function getSubtitle(props) {
|
|||
}
|
||||
|
||||
function getBody(props) {
|
||||
let body = [<span key={'body'}>{props.body}</span>];
|
||||
|
||||
if (props.showWarning) {
|
||||
body.push(getWarning());
|
||||
}
|
||||
|
||||
return (
|
||||
<Card.Body className={'pane-body'}>
|
||||
{props.body}
|
||||
{body}
|
||||
</Card.Body>
|
||||
)
|
||||
}
|
||||
|
||||
export default InfoPane
|
||||
function getWarning() {
|
||||
return (
|
||||
<div className={'info-pane-warning'} key={'warning'}>
|
||||
<WarningIcon/>This option may cause a system to become unstable or
|
||||
change the system's state in undesirable ways. Therefore, this option
|
||||
is not recommended for use in production or other sensitive environments.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export {getDefaultPaneParams, InfoPane}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import {resolveObjectPath} from './utils/ObjectPathResolver';
|
||||
|
||||
function getFullDefinitionByKey(refString, registry, itemKey) {
|
||||
let fullArray = getFullDefinitionsFromRegistry(refString, registry);
|
||||
return fullArray.filter(e => (e.enum[0] === itemKey))[0];
|
||||
}
|
||||
|
||||
// Definitions passed to components only contains value and label,
|
||||
// custom fields like "info" or "links" must be pulled from registry object using this function
|
||||
function getFullDefinitionsFromRegistry(refString, registry) {
|
||||
return getObjectFromRegistryByRef(refString, registry).anyOf;
|
||||
}
|
||||
|
||||
function getObjectFromRegistryByRef(refString, registry) {
|
||||
let refArray = refString.replace('#', '').split('/');
|
||||
return resolveObjectPath(refArray, registry);
|
||||
}
|
||||
|
||||
export {getFullDefinitionByKey, getObjectFromRegistryByRef};
|
|
@ -0,0 +1,9 @@
|
|||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faSync} from '@fortawesome/free-solid-svg-icons';
|
||||
import React from 'react';
|
||||
|
||||
function LoadingIcon() {
|
||||
return <FontAwesomeIcon icon={faSync} className={`spinning-icon loading-icon`} />
|
||||
}
|
||||
|
||||
export default LoadingIcon;
|
|
@ -0,0 +1,41 @@
|
|||
import React from 'react';
|
||||
import {Button} from 'react-bootstrap';
|
||||
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faCheckSquare} from '@fortawesome/free-solid-svg-icons';
|
||||
import {faMinusSquare} from '@fortawesome/free-solid-svg-icons';
|
||||
import {faSquare} from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
const MasterCheckboxState = {
|
||||
NONE: 0,
|
||||
MIXED: 1,
|
||||
ALL: 2
|
||||
}
|
||||
|
||||
function MasterCheckbox(props) {
|
||||
const {
|
||||
title,
|
||||
disabled,
|
||||
onClick,
|
||||
checkboxState
|
||||
} = props;
|
||||
|
||||
let newCheckboxIcon = faCheckSquare;
|
||||
|
||||
if (checkboxState === MasterCheckboxState.NONE) {
|
||||
newCheckboxIcon = faSquare;
|
||||
} else if (checkboxState === MasterCheckboxState.MIXED) {
|
||||
newCheckboxIcon = faMinusSquare;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'master-checkbox'}>
|
||||
<Button key={`${title}-button`} variant={'link'} disabled={disabled} onClick={onClick}>
|
||||
<FontAwesomeIcon icon={newCheckboxIcon}/>
|
||||
</Button>
|
||||
<span className={'header-title'}>{title}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export {MasterCheckboxState, MasterCheckbox};
|
|
@ -0,0 +1,11 @@
|
|||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
|
||||
import React from 'react';
|
||||
|
||||
function WarningIcon() {
|
||||
return (
|
||||
<FontAwesomeIcon className="warning-icon" icon={faExclamationTriangle}/>
|
||||
);
|
||||
}
|
||||
|
||||
export default WarningIcon;
|
|
@ -17,6 +17,7 @@
|
|||
@import 'components/inline-selection/BackButton';
|
||||
@import 'components/inline-selection/CommandDisplay';
|
||||
@import 'components/Icons';
|
||||
@import 'components/Buttons';
|
||||
|
||||
|
||||
// Define custom elements after bootstrap import
|
||||
|
|
|
@ -18,12 +18,14 @@
|
|||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.advanced-multi-select .card-header button {
|
||||
padding-top: 0;
|
||||
.advanced-multi-select .card-header .master-checkbox span {
|
||||
padding-bottom: 0.188rem;
|
||||
}
|
||||
|
||||
.advanced-multi-select .card-header .header-title {
|
||||
font-size: 1.2em;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.advanced-multi-select .choice-block .form-group {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
a.inline-link {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
|
@ -1,3 +1,10 @@
|
|||
.loading-icon {
|
||||
color: $monkey-yellow;
|
||||
font-size: 20px;
|
||||
margin-left: 50%;
|
||||
margin-right: 50%;
|
||||
}
|
||||
|
||||
.spinning-icon {
|
||||
animation: spin-animation 0.5s infinite;
|
||||
display: inline-block;
|
||||
|
|
|
@ -27,3 +27,10 @@
|
|||
margin: 10px 15px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.info-pane-warning {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.info-pane-warning .warning-icon {
|
||||
margin-left: 0em;
|
||||
}
|
||||
|
|
|
@ -57,3 +57,10 @@
|
|||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
text-transform: uppercase;
|
||||
color: #ffc107;
|
||||
font-weight: 900;
|
||||
margin-left: .75em;
|
||||
margin-right: .75em;
|
||||
}
|
||||
|
|
|
@ -14,3 +14,8 @@ div.run-on-os-buttons > .nav-item > .nav-link:hover{
|
|||
color: $monkey-white;
|
||||
background-color: $monkey-alt;
|
||||
}
|
||||
|
||||
.aws-run-button-container {
|
||||
margin-top: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
@ -71,10 +71,12 @@ module.exports = {
|
|||
publicPath: '/'
|
||||
},
|
||||
devServer: {
|
||||
host: '0.0.0.0',
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'https://localhost:5000',
|
||||
secure: false
|
||||
secure: false,
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ botocore==1.17.54
|
|||
cffi>=1.8,!=1.11.3
|
||||
dpath>=2.0
|
||||
flask>=1.1
|
||||
gevent>=20.9.0
|
||||
ipaddress>=1.0.23
|
||||
jsonschema==3.2.0
|
||||
mongoengine==0.20
|
||||
|
@ -20,7 +21,6 @@ requests>=2.24
|
|||
ring>=0.7.3
|
||||
stix2>=2.0.2
|
||||
six>=1.13.0
|
||||
tornado>=6.0.4
|
||||
tqdm>=4.47
|
||||
virtualenv>=20.0.26
|
||||
werkzeug>=1.0.1
|
||||
|
|
|
@ -3,5 +3,5 @@ log_cli = 1
|
|||
log_cli_level = DEBUG
|
||||
log_cli_format = %(asctime)s [%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s
|
||||
log_cli_date_format=%H:%M:%S
|
||||
addopts = -v --capture=sys
|
||||
addopts = -v --capture=sys --ignore=common/cloud/scoutsuite
|
||||
norecursedirs = node_modules dist
|
||||
|
|
Loading…
Reference in New Issue