Package modules :: Package machinery :: Module physical
[hide private]
[frames] | no frames]

Source Code for Module modules.machinery.physical

  1  # Copyright (C) 2012-2014 The MITRE Corporation. 
  2  # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org 
  3  # See the file 'docs/LICENSE' for copying permission. 
  4   
  5  import socket 
  6  import logging 
  7  import xmlrpclib 
  8  import subprocess 
  9   
 10  log = logging.getLogger(__name__) 
 11   
 12  try: 
 13      import bs4 
 14      import requests 
 15      import wakeonlan.wol 
 16      HAVE_FOG = True 
 17  except ImportError: 
 18      HAVE_FOG = False 
 19      log.critical( 
 20          "The bs4, requests, and wakeonlan Python libraries are required for " 
 21          "proper FOG integration with Cuckoo (please install them through " 
 22          "`pip install bs4 requests wakeonlan`)." 
 23      ) 
 24   
 25  from lib.cuckoo.common.abstracts import Machinery 
 26  from lib.cuckoo.common.constants import CUCKOO_GUEST_PORT 
 27  from lib.cuckoo.common.exceptions import CuckooCriticalError 
 28  from lib.cuckoo.common.exceptions import CuckooMachineError 
 29  from lib.cuckoo.common.utils import TimeoutServer 
 30   
31 -class Physical(Machinery):
32 """Manage physical sandboxes.""" 33 34 # Physical machine states. 35 RUNNING = "running" 36 STOPPED = "stopped" 37 ERROR = "error" 38
39 - def _initialize_check(self):
40 """Ensures that credentials have been entered into the config file. 41 @raise CuckooCriticalError: if no credentials were provided or if 42 one or more physical machines are offline. 43 """ 44 # TODO This should be moved to a per-machine thing. 45 if not self.options.physical.user or not self.options.physical.password: 46 raise CuckooCriticalError( 47 "Physical machine credentials are missing, please add it to " 48 "the Physical machinery configuration file." 49 ) 50 51 self.fog_init() 52 53 for machine in self.machines(): 54 status = self._status(machine.label) 55 if status == self.STOPPED: 56 # Send a Wake On Lan message (if we're using FOG). 57 self.wake_on_lan(machine.label) 58 elif status == self.ERROR: 59 raise CuckooMachineError( 60 "Unknown error occurred trying to obtain the status of " 61 "physical machine %s. Please turn it on and check the " 62 "Cuckoo Agent." % machine.label 63 )
64
65 - def _get_machine(self, label):
66 """Retrieve all machine info given a machine's name. 67 @param label: machine name. 68 @return: machine dictionary (id, ip, platform, ...). 69 @raises CuckooMachineError: if no machine is available with the given label. 70 """ 71 for m in self.machines(): 72 if label == m.label: 73 return m 74 75 raise CuckooMachineError("No machine with label: %s." % label)
76
77 - def start(self, label, task):
78 """Start a physical machine. 79 @param label: physical machine name. 80 @param task: task object. 81 @raise CuckooMachineError: if unable to start. 82 """ 83 # Check to ensure a given machine is running 84 log.debug("Checking if machine %r is running.", label) 85 status = self._status(label) 86 if status == self.RUNNING: 87 log.debug("Machine already running: %s.", label) 88 elif status == self.STOPPED: 89 self._wait_status(label, self.RUNNING) 90 else: 91 raise CuckooMachineError("Error occurred while starting: " 92 "%s (STATUS=%s)" % (label, status))
93
94 - def stop(self, label):
95 """Stops a physical machine. 96 @param label: physical machine name. 97 @raise CuckooMachineError: if unable to stop. 98 """ 99 # Since we are 'stopping' a physical machine, it must 100 # actually be rebooted to kick off the re-imaging process. 101 creds = "%s%%%s" % ( 102 self.options.physical.user, self.options.physical.password 103 ) 104 105 if self._status(label) == self.RUNNING: 106 log.debug("Rebooting machine: %s.", label) 107 machine = self._get_machine(label) 108 109 args = [ 110 "net", "rpc", "shutdown", "-I", machine.ip, 111 "-U", creds, "-r", "-f", "--timeout=5" 112 ] 113 output = subprocess.check_output(args) 114 115 if "Shutdown of remote machine succeeded" not in output: 116 raise CuckooMachineError("Unable to initiate RPC request") 117 else: 118 log.debug("Reboot success: %s." % label) 119 120 # Deploy a clean image through FOG, assuming we're using FOG. 121 self.fog_queue_task(label)
122
123 - def _list(self):
124 """Lists physical machines installed. 125 @return: physical machine names list. 126 """ 127 active_machines = [] 128 for machine in self.machines(): 129 if self._status(machine.label) == self.RUNNING: 130 active_machines.append(machine.label) 131 132 return active_machines
133
134 - def _status(self, label):
135 """Gets current status of a physical machine. 136 @param label: physical machine name. 137 @return: status string. 138 """ 139 # For physical machines, the agent can either be contacted or not. 140 # However, there is some information to be garnered from potential 141 # exceptions. 142 log.debug("Getting status for machine: %s.", label) 143 machine = self._get_machine(label) 144 145 # The status is only used to determine whether the Guest is running 146 # or whether it is in a stopped status, therefore the timeout can most 147 # likely be fairly arbitrary. TODO This is a temporary fix as it is 148 # not compatible with the new Cuckoo Agent, but it will have to do. 149 url = "http://{0}:{1}".format(machine.ip, CUCKOO_GUEST_PORT) 150 server = TimeoutServer(url, allow_none=True, timeout=60) 151 152 try: 153 status = server.get_status() 154 except xmlrpclib.Fault as e: 155 # Contacted Agent, but it threw an error. 156 log.debug("Agent error: %s (%s) (Error: %s).", 157 machine.id, machine.ip, e) 158 return self.ERROR 159 except socket.error as e: 160 # Could not contact agent. 161 log.debug("Agent unresponsive: %s (%s) (Error: %s).", 162 machine.id, machine.ip, e) 163 return self.STOPPED 164 except Exception as e: 165 # TODO Handle this better. 166 log.debug("Received unknown exception: %s.", e) 167 return self.ERROR 168 169 # If the agent responded successfully, then the physical machine 170 # is running 171 if status: 172 return self.RUNNING 173 174 return self.ERROR
175
176 - def fog_query(self, uri, data={}):
177 """Wrapper around requests for simplifying FOG API access. Assuming 178 you can call what FOG is providing an API.""" 179 url = "http://%s/fog/management/index.php?%s" % ( 180 self.options.fog.hostname, uri, 181 ) 182 183 data.update({ 184 "uname": self.options.fog.username, 185 "upass": self.options.fog.password, 186 }) 187 188 return requests.post(url, data=data)
189
190 - def fog_init(self):
191 """Initiate by indexing FOG regarding all available machines.""" 192 self.fog_machines = {} 193 if not HAVE_FOG or self.options.fog.hostname == "none": 194 return 195 196 # TODO Handle exceptions such as not being able to connect. 197 r = self.fog_query("node=tasks&sub=listhosts") 198 199 # Parse the HTML. 200 b = bs4.BeautifulSoup(r.content, "html.parser") 201 if not b.find_all("table"): 202 raise CuckooCriticalError( 203 "The supplied FOG username and/or password do not allow us " 204 "to login into FOG, please configure the correct credentials." 205 ) 206 207 # Mapping for physical machine hostnames to their mac address and uri 208 # for "downloading" a safe image onto the host. Great piece of FOG API 209 # usage here. 210 for row in b.find_all("table")[0].find_all("tr")[1:]: 211 hostname, macaddr, download, upload, advanced = row.find_all("td") 212 self.fog_machines[hostname.text] = ( 213 macaddr.text, next(download.children).attrs["href"][1:], 214 ) 215 216 # Check whether all our machines are available on FOG. 217 for machine in self.machines(): 218 if machine.label not in self.fog_machines: 219 raise CuckooMachineError( 220 "The physical machine %s has not been defined in FOG, " 221 "please investigate and configure the configuration " 222 "correctly." % machine.label 223 )
224
225 - def fog_queue_task(self, hostname):
226 """Queues a task with FOG to deploy the given machine after reboot.""" 227 if hostname in self.fog_machines: 228 macaddr, download = self.fog_machines[hostname] 229 self.fog_query(download)
230
231 - def wake_on_lan(self, hostname):
232 """Start a machine that's currently shutdown.""" 233 if hostname in self.fog_machines: 234 macaddr, download = self.fog_machines[hostname] 235 wakeonlan.wol.send_magic_packet(macaddr)
236