def execute(self): """ Executes the task. """ if not ServerConfig.objects.bool_by_key('orthos.debug.setup.execute'): logger.warning( "Disabled: set 'orthos.debug.setup.execute' to 'true'") return logger.debug('Calling setup script...') try: machine = Machine.objects.get(fqdn=self.fqdn) tftp_server = machine.fqdn_domain.tftp_server if not tftp_server: logger.warning("No TFTP server available for '{}'".format( machine.fqdn_domain.name)) return command_template = ServerConfig.objects.by_key( 'setup.execute.command') context = Context({'machine': machine, 'choice': self.choice}) command = Template(command_template).render(context) logger.debug("Initialize setup {}@{}: {}:{}".format( self.choice, machine.fqdn, tftp_server.fqdn, command)) tftp_server = SSH(tftp_server.fqdn) tftp_server.connect() stdout, stderr, exitstatus = tftp_server.execute(command) tftp_server.close() if exitstatus != 0: logger.warning( "Creating setup configuration failed for '{}'".format( machine)) return # reboot machine finally machine.reboot() except SSH.Exception as exception: logger.error(exception) except Machine.DoesNotExist: logger.error("Machine does not exist: fqdn={}".format(self.fqdn)) except Exception as e: logger.exception(e)
def abuild_test(fqdn): """ Checks if Autobuild is running. """ conn = None try: conn = SSH(fqdn) conn.connect() pids, stderr, exitstatus = conn.execute( r"ps -e -o pid,cmd | awk '/.*\/usr\/sbin\/autobuild.*/{print $1}'") if len(pids) > 0: return True except Exception as e: logger.warning("SSH login failed for '{}': {}".format(fqdn, e)) return False finally: if conn: conn.close() return False
def ssh_shutdown(self, user=None, reboot=False): """ Powers off/reboots the machine using SSH. """ from utils.ssh import SSH if not reboot: option = '--poweroff' else: option = '--reboot' machine = SSH(self.fqdn) machine.connect() command = 'shutdown {} now'.format(option) stdout, stderr, exitstatus = machine.execute(command, retry=False) machine.close() if exitstatus != 0: return False return True
def _perform(self, action): """ Common implementation for on, off and reset. """ name = self.machine.hostname virsh = 'virsh -c lxc:///' conn = None result = False if not self.machine.hypervisor.fqdn: logger.error("No hypervisor system found") raise Exception("No hypervisor found") conn = SSH(self.machine.hypervisor.fqdn) conn.connect() if action == 'status': stdout, stderr, exitstatus = conn.execute( '{} list --all'.format(virsh)) if exitstatus != 0: logger.error(''.join(stderr)) raise Exception(''.join(stderr)) for line in stdout[2:]: columns = line.strip().split() if columns[1] == name: return { 'running': self.Status.ON, 'shut': self.Status.OFF, 'paused': self.Status.PAUSED }.get(columns[2], 0) raise Exception("Couldn't find domain '{}'!".format(name)) elif action == 'off': stdout, stderr, exitstatus = conn.execute('{} destroy {}'.format( virsh, name)) if exitstatus == 0: logger.debug("Virtual machine '{}' stopped".format(name)) result = True else: logger.error(''.join(stderr)) raise Exception(''.join(stderr)) elif action == 'on': stdout, stderr, exitstatus = conn.execute('{} start {}'.format( virsh, name)) if exitstatus == 0: logger.debug("Virtual machine '{}' started".format(name)) result = True else: logger.error(''.join(stderr)) raise Exception(''.join(stderr)) else: logger.warning("Action '{}' does not exist".format(action)) result = False if conn: conn.close() return result
def execute(self): """ Executes the task. """ from data.models import Machine, SerialConsole if not ServerConfig.objects.bool_by_key( 'orthos.debug.serialconsole.write'): logger.warning( "Disabled: set 'orthos.debug.serialconsole.write' to 'true'") return try: cscreen_server = Machine.objects.get(fqdn=self.fqdn) except Machine.DoesNotExist: logger.warning("Serial console server does not exist: {}".format( self.fqdn)) conn = None try: conn = SSH(cscreen_server.fqdn) conn.connect() stdout, stderr, exitstatus = conn.execute( 'sudo touch /etc/cscreenrc_allow_update') if exitstatus != 0: raise Exception( "Couldn't lock cscreen ('touch /etc/cscreenrc_allow_update')" ) new_content = '' for serialconsole in SerialConsole.cscreen.get(cscreen_server): new_content += serialconsole.get_comment_record() + '\n' new_content += serialconsole.get_command_record() + '\n' screenrc_file = '/etc/cscreenrc' # create `/etc/cscreenrc` if it doesn't exist stdout, stderr, exitstatus = conn.execute( '[ -e "{}"]'.format(screenrc_file)) orthos_inline_begin = ServerConfig.objects.by_key( 'orthos.configuration.inline.begin') orthos_inline_end = ServerConfig.objects.by_key( 'orthos.configuration.inline.end') if exitstatus != 0: stdout, stderr, exitstatus = conn.execute( 'echo "{}\n{}" > {}'.format(orthos_inline_begin, screenrc_file, orthos_inline_end)) if exitstatus != 0: raise Exception("Couldn't create CScreen file ('{}')".format( screenrc_file)) # Save backup file which is used later by an invoked script # to determine the changes and update the running screen # session (add, remove or restart modified entries). stdout, stderr, exitstatus = conn.execute( 'sudo cp {} {}.old'.format(screenrc_file, screenrc_file)) cscreen = conn.get_file(screenrc_file, 'r') buffer = '' in_replace = False for line in cscreen.readlines(): if not in_replace and line.startswith(orthos_inline_begin): buffer += line + new_content in_replace = True elif in_replace and line.startswith(orthos_inline_end): buffer += line in_replace = False elif not in_replace: buffer += line cscreen.close() cscreen = conn.get_file(screenrc_file, 'w') buffer = buffer.strip('\n') print(buffer, file=cscreen) cscreen.close() stdout, stderr, exitstatus = conn.execute( 'sudo /usr/bin/cscreen -u') logger.info("CScreen update exited with: {}".format(exitstatus)) stdout, stderr, exitstatus = conn.execute( 'sudo rm -f /etc/cscreenrc_allow_update') if exitstatus != 0: raise Exception( "Couldn't unlock CScreen ('rm /etc/cscreenrc_allow_update')" ) except SSH.Exception as exception: logger.error(exception) except IOError as exception: logger.error(exception) finally: if conn: conn.close()
class CobblerServer: def __init__(self, fqdn, domain): self._fqdn = fqdn self._conn = None self._domain = domain self._cobbler_path = ServerConfig.objects.by_key("cobbler.command") def connect(self): """ Connect to DHCP server via SSH. """ if not self._conn: self._conn = SSH(self._fqdn) self._conn.connect() def close(self): """ Close connection to DHCP server. """ if self._conn: self._conn.close() def deploy(self): self.connect() if not self.is_installed(): raise SystemError("No Cobbler service found: {}".format(self._fqdn)) machines = Machine.active_machines.filter(fqdn_domain=self._domain.pk) cobbler_machines = self.get_machines() cobbler_commands = [] for machine in machines: if machine.fqdn in cobbler_machines: cobbler_commands.append(get_cobbler_update_command(machine, self._cobbler_path)) else: cobbler_commands.append(get_cobbler_add_command(machine, self._cobbler_path)) for command in cobbler_commands: # TODO: Convert this to a single ssh call (performance) _, stderr, exitcode = self._conn.execute(command) if exitcode: logger.error("failed to execute %s on %s", command, self._fqdn) self.close() def is_installed(self): """ Check if Cobbler server is available. """ if self._conn.check_path(self._cobbler_path, '-x'): return True return False def is_running(self): """ Check if the Cobbler daemon is running via the cobbler version command """ command = f"{self._cobbler_path} version" _, _, exitstatus = self._conn.execute(command) if exitstatus == 0: return True return False def get_machines(self): stdout, stderr, exitstatus = self._conn.execute( "{cobbler} system list".format(cobbler=self._cobbler_path)) if exitstatus: logger.warning("system list failed on %s with %s", self._fqdn, stderr) raise CobblerException("system list failed on {server}".format(server=self._fqdn)) clean_out = [system.strip(' \n\t') for system in stdout] return clean_out
class Libvirt(VirtualizationAPI): class Meta: proxy = True VIRSH = 'virsh -c qemu:///system' IGNORE_STDERR = [ 'domain is not running', 'no domain with matching name' ] QEMU_IMG_CONVERT = '/usr/bin/qemu-img convert -O qcow2 -o preallocation=metadata {0}.tmp {0}' def __init__(self): self.conn = None def get_image_list(self): """ Returns the available architectures and the full image list (over all available architectures). Return format: ( ['<arch1>', '<arch2>', ...], [('<value>', '<option>'), ...] ) """ from data.models import ServerConfig architectures = [self.host.architecture.name] image_directory = ServerConfig.objects.by_key('virtualization.libvirt.images.directory') image_list = [] try: for architecture in architectures: directory = '{}/{}/'.format(image_directory.rstrip('/'), architecture) for image in os.listdir(directory): path = directory + image size = os.path.getsize(path) atime = str(date.fromtimestamp(os.path.getmtime(path))) if size < (1024**3): size = int(size / (1024**2)) size = '{}M'.format(size) else: size = int(size / (1024**3)) size = '{}G'.format(size) pretty_image = image.split('.')[0] image_list.append((image, '{} ({} {})'.format(pretty_image, atime, size))) except FileNotFoundError as e: logger.exception(e) return (architectures, image_list) def connect(function): """ Create SSH connection if needed. """ def decorator(self, *args, **kwargs): from utils.ssh import SSH if not self.conn: self.conn = SSH(self.host.fqdn) self.conn.connect() return function(self, *args, **kwargs) return decorator @connect def _execute(self, command): return self.conn.execute(command) def check_connection(self): """ Check libvirt connection (running libvirt). """ stdout, stderr, exitstatus = self._execute('{} version'.format(self.VIRSH)) if exitstatus == 0: return True return False def get_list(self, parameters='--all'): """ Return `virsh list` output. """ stdout, stderr, exitstatus = self._execute('{} list {}'.format(self.VIRSH, parameters)) if exitstatus == 0: return ''.join(stdout) else: raise Exception(''.join(stderr)) return False def check_network_bridge(self, bridge='br0'): """ Execute `create_bridge.sh` script remotely and try to set up bridge if it doesn't exist. Returns true if the bridge is available, false otherwise. """ stdout, stderr, exitstatus = self.conn.execute_script_remote('create_bridge.sh') if exitstatus != 0: raise Exception(''.join(stderr)) stdout, stderr, exitstatus = self.conn.execute('brctl show') if exitstatus != 0: raise Exception(''.join(stderr)) for line in stdout: if line.startswith(bridge): return True raise False def generate_hostname(self): """ Generate domain name (hostname). Check hostnames against Orthos machines and libvirt `virsh list`. """ hostname = None occupied_hostnames = set(vm.hostname for vm in self.host.get_virtual_machines()) libvirt_list = self.get_list() for line in libvirt_list.split('\n')[2:]: columns = line.strip().split() if len(columns) > 0: domain_name = columns[1] occupied_hostnames.add(domain_name) for i in range(1, self.host.vm_max + 1): hostname_ = '{}-{}'.format(self.host.hostname, i) if hostname_ not in occupied_hostnames: hostname = hostname_ break if hostname is None: raise Exception("All hostnames (domain names) busy!") return hostname def generate_networkinterfaces(self, amount=1, bridge='br0', model='virtio'): """ Generate networkinterfaces. """ from data.models import NetworkInterface networkinterfaces = [] for i in range(amount): mac_address = get_random_mac_address() while NetworkInterface.objects.filter(mac_address=mac_address).count() != 0: mac_address = get_random_mac_address() networkinterface = NetworkInterface(mac_address=mac_address) networkinterface.bridge = bridge networkinterface.model = model networkinterfaces.append(networkinterface) return networkinterfaces def copy_image(self, image, disk_image): """ Copy and allocate disk image. """ stdout, stderr, exitstatus = self.conn.execute('cp {} {}.tmp'.format(image, disk_image)) if exitstatus != 0: return False stdout, stderr, exitstatus = self.conn.execute(self.QEMU_IMG_CONVERT.format(disk_image)) if exitstatus != 0: return False stdout, stderr, exitstatus = self.conn.execute('rm -rf {}.tmp'.format(disk_image)) if exitstatus != 0: return False return True def delete_disk_image(self, disk_image): """ Delete the old disk image. """ stdout, stderr, exitstatus = self.conn.execute('rm -rf {}'.format(disk_image)) if exitstatus != 0: return False return True def calculate_vcpu(self): """ Return virtual CPU amount. """ vcpu = 1 host_cpu_cores = self.host.cpu_cores if host_cpu_cores is not None: vcpu = int((host_cpu_cores - 2) / self.host.vm_max) if vcpu == 0: vcpu = 1 return vcpu def check_memory(self, memory_amount): """ Check if memory amount for VM is available on host. Reserve 2GB of memory for host system. """ host_ram_amount = self.host.ram_amount host_reserved_ram_amount = 2048 if host_ram_amount: if memory_amount > (host_ram_amount - host_reserved_ram_amount): raise Exception("Host system has only {}MB of memory!".format(memory_amount)) else: raise Exception("Can't detect memory size of host system '{}'".format(self.host)) return True def execute_virt_install(self, *args, dry_run=True, **kwargs): """ Run `virt-install` command. """ command = '/usr/bin/virt-install ' command += '--name {hostname} ' command += '--vcpus {vcpu} ' command += '--memory {memory} ' disk_ = '--disk {},'.format(kwargs['disk']['image']) disk_ += 'size={},'.format(kwargs['disk']['size']) disk_ += 'format={},'.format(kwargs['disk']['format']) disk_ += 'sparse={},'.format(kwargs['disk']['sparse']) disk_ += 'bus={} '.format(kwargs['disk']['bus']) command += disk_ for networkinterface in kwargs.get('networkinterfaces', []): networkinterface_ = '--network model={},'.format(networkinterface.model) networkinterface_ += 'bridge={},'.format(networkinterface.bridge) networkinterface_ += 'mac={} '.format(networkinterface.mac_address) command += networkinterface_ command += '{boot} ' vnc = kwargs.get('vnc', None) if vnc and vnc['enabled']: command += '--graphics vnc,listen=0.0.0.0,port={} '.format(vnc['port']) command += kwargs.get('parameters', '') if dry_run: command += '--dry-run' command = command.format(**kwargs) logger.debug(command) stdout, stderr, exitstatus = self.conn.execute(command) if exitstatus != 0: raise Exception(''.join(stderr)) return True def _create(self, vm, *args, **kwargs): """ Wrapper function for creating a VM. Steps: - check connection to host - check maxinmum VM number limit - check network bridge - check image source directory (if needed) - check Open Virtual Machine Firmware (OVMF) binary (if needed) - check memory size - generate hostname (=domain name) - copy image to disk image (if needed) - run `virt-install` """ from data.models import ServerConfig from data.models import NetworkInterface bridge = ServerConfig.objects.by_key('virtualization.libvirt.bridge') image_directory = ServerConfig.objects.by_key('virtualization.libvirt.images.directory') disk_image_directory = '/abuild/orthos-vm-images/' disk_image = '{}/{}.qcow2'.format(disk_image_directory.rstrip('/'), '{}') ovmf = ServerConfig.objects.by_key('virtualization.libvirt.ovmf.path') image_directory = '{}/{}/'.format( image_directory.rstrip('/'), kwargs['architecture'] ) if not self.check_connection(): raise Exception("Host system not reachable!") if self.host.get_virtual_machines().count() >= self.host.vm_max: raise Exception("Maximum number of VMs reached!") if not self.check_network_bridge(bridge=bridge): raise Exception("Network bridge setup failed!") if not kwargs['image'] is None: if not self.conn.check_path(image_directory, '-e'): raise Exception("Image source directory missing on host system!") if not self.conn.check_path(disk_image_directory, '-w'): raise Exception( "Image disk directory missing on host system: {}!".format(disk_image_directory) ) if kwargs['uefi_boot']: if not self.conn.check_path(ovmf, '-e'): raise Exception("OVMF file not found: '{}'!".format(ovmf)) boot = '--boot loader={},network'.format(ovmf) else: boot = '--boot network,hd,menu=off' self.check_memory(kwargs['ram_amount']) vm.hostname = self.generate_hostname() vm.fqdn = '{}.{}'.format(vm.hostname, self.host.fqdn_domain.name) vnc_port = 5900 + int(vm.hostname.split('-')[1]) vm.vnc = { 'enabled': kwargs['vnc'], 'port': vnc_port } vm.cpu_cores = self.calculate_vcpu() vm.ram_amount = kwargs['ram_amount'] disk_image = disk_image.format(vm.hostname) if kwargs['image'] is not None: image = '{}/{}'.format(image_directory.rstrip('/'), kwargs['image']) if not self.copy_image(image, disk_image): raise Exception("Couldn't copy image: {} > {}!".format(image, disk_image)) else: self.delete_disk_image(disk_image) disk = { 'image': disk_image, 'size': kwargs['disk_size'], 'format': 'qcow2', 'sparse': True, 'bus': 'virtio' } networkinterfaces = self.generate_networkinterfaces( amount=kwargs['networkinterfaces'], bridge=bridge ) parameters = '--events on_reboot=restart,on_poweroff=destroy ' parameters += '--import ' parameters += '--noautoconsole ' parameters += '--autostart ' parameters += kwargs['parameters'] self.execute_virt_install( hostname=vm.hostname, vcpu=vm.cpu_cores, memory=vm.ram_amount, disk=disk, networkinterfaces=networkinterfaces, boot=boot, vnc=vm.vnc, parameters=parameters, dry_run=False ) vm.unsaved_networkinterfaces = [] for networkinterface in networkinterfaces: vm.unsaved_networkinterfaces.append(networkinterface) return True def _remove(self, vm): """ Wrapper function for removing a VM (destroy domain > undefine domain). """ if not self.check_connection(): raise Exception("Host system not reachable!") self.destroy(vm) self.undefine(vm) return True def destroy(self, vm): """ Destroy VM on host system. Ignore `domain is not running` error and proceed. """ stdout, stderr, exitstatus = self._execute('{} destroy {}'.format(self.VIRSH, vm.hostname)) if exitstatus != 0: stderr = ''.join(stderr) if not any(line in stderr for line in self.IGNORE_STDERR): raise Exception(stderr) return True def undefine(self, vm): """ Undefine VM on host system. """ stdout, stderr, exitstatus = self._execute('{} undefine {}'.format(self.VIRSH, vm.hostname)) if exitstatus != 0: stderr = ''.join(stderr) if not any(line in stderr for line in self.IGNORE_STDERR): raise Exception(stderr) return True
def get_pci_devices(fqdn): """ Retrieves all PCI devices. """ def get_pci_device_by_slot(pci_devices, slot): """ Returns the PCI device by slot. """ for dev in pci_devices: pci_slot = dev.slot if not pci_slot: continue pci_slot = pci_slot.strip() # pci domain hacks if len(pci_slot) < 8: pci_slot = '0000:' + pci_slot if len(slot) < 8: slot = '0000:' + slot if pci_slot == slot: return dev return None from data.models import PCIDevice try: machine = Machine.objects.get(fqdn=fqdn) except Machine.DoesNotExist: logger.warning("Machine '{}' does not exist".format(fqdn)) return False conn = None timer = None try: conn = SSH(fqdn) conn.connect() timer = threading.Timer(5 * 60, conn.close) timer.start() logger.debug("Collect PCI devices for '{}'...".format(machine.fqdn)) pci_devices = [] chunk = '' stdout, stderr, exitstatus = conn.execute('lspci -mmvn') for line in stdout: if len(line.strip()) == 0: pci_devices.append(PCIDevice.from_lspci_mmnv(chunk)) chunk = '' else: chunk += line # drivers for PCI devices from hwinfo in_pci_device = False current_busid = None if machine.hwinfo: for line in machine.hwinfo.splitlines(): if re.match(r'^\d+: PCI', line): in_pci_device = True continue if len(line.strip()) == 0: in_pci_device = False current_busid = None continue if not in_pci_device: continue match = re.match(r' SysFS BusID: ([0-9a-fA-F.:]+)', line) if match: current_busid = match.group(1) match = re.match(r' Driver: "([^"]*)"', line) if match and current_busid: pcidev = get_pci_device_by_slot(pci_devices, current_busid) if pcidev: pcidev.drivermodule = match.group(1) match = re.match(r' Driver Modules: "([^"]*)"', line) if match and current_busid: pcidev = get_pci_device_by_slot(pci_devices, current_busid) if pcidev: pcidev.drivermodule = match.group(1) for pci_device in pci_devices: pci_device.machine = machine logger.debug("Collected {} PCI devices for '{}'".format( len(pci_devices), machine.fqdn)) return pci_devices except Exception as e: logger.exception("{} ({})".format(fqdn, e)) return False finally: if conn: conn.close() if timer: timer.cancel()
def get_status_ip(fqdn): """ Retrieves information of the systems IPv4/IPv6 status. """ try: machine = Machine.objects.get(fqdn=fqdn) except Machine.DoesNotExist: logger.warning("Machine '{}' does not exist".format(fqdn)) return False machine_ = Machine() conn = None timer = None try: conn = SSH(machine.fqdn) conn.connect() timer = threading.Timer(5 * 60, conn.close) timer.start() logger.debug("Check IPv4/IPv6 status...") stdout, stderr, exitstatus = conn.execute('/sbin/ip a') devices = {} current_device = None addresses = {'inet': [], 'inet6': []} for line in stdout: match = re.match(r'^\d+:\s+([a-zA-Z0-9]+):\s+<.*>\s(.*)\n', line) if match: current_device = match.group(1) devices[current_device] = { 'mac_address': None, 'inet': None, 'inet6': None, 'flags': None } devices[current_device]['flags'] = match.group(2).split() continue line = line.lstrip() match = re.match(r'inet ([0-9.]{7,15})\/.*scope', line) if match: if devices[current_device]['inet'] is None: devices[current_device]['inet'] = [] devices[current_device]['inet'].append(match.group(1)) continue match = re.match(r'inet6 ([a-f0-9:]*)\/[0-9]+ scope', line) if match: if devices[current_device]['inet6'] is None: devices[current_device]['inet6'] = [] devices[current_device]['inet6'].append(match.group(1)) continue match = re.match('link/ether ([a-f0-9:]{17}) brd', line) if match: devices[current_device]['mac_address'] = match.group(1).upper() for device, values in devices.items(): if values['mac_address'] is None: continue # ignore device if hooking up another if any(device in values['flags'] for device in devices.keys()): continue if values['mac_address'] == machine.mac_address: if values['inet'] is None: machine_.status_ipv4 = Machine.StatusIP.AF_DISABLED elif machine.ipv4 not in values['inet']: machine_.status_ipv4 = Machine.StatusIP.NO_ADDRESS if [ ipv4 for ipv4 in values['inet'] if not ipv4.startswith('127.0.0.1') ]: machine_.status_ipv4 = Machine.StatusIP.ADDRESS_MISMATCH elif machine.ipv4 in values['inet']: machine_.status_ipv4 = Machine.StatusIP.CONFIRMED else: machine_.status_ipv4 = Machine.StatusIP.MISSING if values['inet6'] is None: machine_.status_ipv6 = Machine.StatusIP.AF_DISABLED elif machine.ipv6 not in values['inet6']: machine_.status_ipv6 = Machine.StatusIP.NO_ADDRESS if [ ipv6 for ipv6 in values['inet6'] if not ipv6.startswith('fe80::') ]: machine_.status_ipv6 = Machine.StatusIP.ADDRESS_MISMATCH elif machine.ipv6 in values['inet6']: machine_.status_ipv6 = Machine.StatusIP.CONFIRMED addresses['inet'].append(values['inet']) addresses['inet6'].append(values['inet6']) if machine_.status_ipv4 == Machine.StatusIP.NO_ADDRESS: if machine.ipv4 in addresses['inet']: machine_.status_ipv4 = Machine.StatusIP.MAC_MISMATCH if machine_.status_ipv6 == Machine.StatusIP.NO_ADDRESS: if machine.ipv6 in addresses['inet6']: machine_.status_ipv6 = Machine.StatusIP.MAC_MISMATCH return machine_ except Exception as e: logger.error("{} ({})".format(fqdn, e)) return False finally: if conn: conn.close() if timer: timer.cancel() return None
def get_networkinterfaces(fqdn): """ Retrieves information of the systems network interfaces. """ try: machine = Machine.objects.get(fqdn=fqdn) except Machine.DoesNotExist: logger.warning("Machine '{}' does not exist".format(fqdn)) return False conn = None timer = None try: conn = SSH(machine.fqdn) conn.connect() timer = threading.Timer(5 * 60, conn.close) timer.start() # Network interfaces logger.debug("Collect network interfaces...") stdout, stderr, exitstatus = conn.execute('hwinfo --network') interfaces = [] interface = None for line in stdout: if len(line) > 0 and line[0] != ' ' and line[0] != '\t': if interface and interface.mac_address and\ interface.driver_module not in ('bridge', 'tun'): interfaces.append(interface) interface = NetworkInterface() else: match = re.match(r'\s+Driver: "(\w+)"', line) if match: interface.driver_module = match.group(1) continue match = re.match(r'\s+SysFS ID: ([/\w.]+)', line) if match: interface.sysfs = match.group(1) continue match = re.match(r'\s+HW Address: ([0-9a-fA-F:]+)', line) if match: interface.mac_address = match.group(1).upper() continue match = re.match(r'\s+Device File: ([\w.]+)', line) if match: interface.name = match.group(1) continue if interface and interface.mac_address and\ interface.driver_module not in ('bridge', 'tun'): interfaces.append(interface) for interface in interfaces: if interface.sysfs is None: continue path = '/sys/{}/type'.format(interface.sysfs) arp_type = ''.join(conn.read_file(path)) if arp_type == ARPHRD_IEEE80211: continue stdout, stderr, exitstatus = conn.execute('ethtool %s' % (interface.name)) for line in stdout: match = re.match(r'\s+Port: (.+)', line) if match: interface.ethernet_type = match.group(1) return interfaces except Exception as e: logger.error("{} ({})".format(fqdn, e)) return False finally: if conn: conn.close() if timer: timer.cancel() return None
def get_hardware_information(fqdn): """ Retrieves information of the system. """ try: machine = Machine.objects.get(fqdn=fqdn) except Machine.DoesNotExist: logger.warning("Machine '{}' does not exist".format(fqdn)) return # set needed values for several checks from original machine machine_ = Machine(architecture=machine.architecture) conn = None timer = None try: conn = SSH(fqdn) conn.connect() timer = threading.Timer(5 * 60, conn.close) timer.start() # CPUs logger.debug("Get CPU number...") output, stderr, exitstatus = conn.execute_script_remote( 'machine_get_cpu_number.sh') if output: for line in output: if line.startswith('SOCKETS'): machine_.cpu_physical = int(line.split('=')[1]) elif line.startswith('CORES'): machine_.cpu_cores = int(line.split('=')[1]) elif line.startswith('THREADS'): machine_.cpu_threads = int(line.split('=')[1]) logger.debug("Get CPU type...") output, stderr, exitstatus = conn.execute_script_remote( 'machine_get_cpu_type.sh') if output and output[0]: machine_.cpu_model = output[0].strip() logger.debug("Get CPU flags...") output, stderr, exitstatus = conn.execute_script_remote( 'machine_get_cpu_flags.sh') if output and output[0]: machine_.cpu_flags = output[0].strip() logger.debug("Get CPU speed...") output, stderr, exitstatus = conn.execute_script_remote( 'machine_get_cpu_speed.sh') if output and output[0]: machine_.cpu_speed = Decimal(int(output[0].strip()) / 1000000) logger.debug("Get CPU ID...") output, stderr, exitstatus = conn.execute_script_remote( 'machine_get_cpu_id.sh') if output and output[0]: machine_.cpu_id = output[0].strip() # EFI logger.debug("Check for EFI...") try: efi_file = conn.get_file('/sys/firmware/efi', 'r') efi_file.close() machine_.efi = True except IOError: machine_.efi = False # Memory logger.debug("Get RAM amount...") for line in conn.read_file('/proc/meminfo'): if line.startswith('MemTotal'): machine_.ram_amount = int(int(line.split()[1]) / 1024) # Virtualization capability VM_HOST_MIN_RAM_MB = 7000 machine_.vm_capable = False # Virtualization: x86 logger.debug("Check for VM capability...") if machine_.architecture_id == Architecture.Type.X86_64: cpu_flags = machine_.cpu_flags if cpu_flags: cpu_flags = cpu_flags.upper() if ((cpu_flags.find('VMX') >= 0 or cpu_flags.find('SVM') >= 0) and int(machine_.ram_amount) > VM_HOST_MIN_RAM_MB): machine_.vm_capable = True # Virtualization: ppc64le if machine_.architecture_id == Architecture.Type.PPC64LE: for line in conn.read_file('/proc/cpuinfo'): if line.startswith('firmware') and 'OPAL' in line: machine_.vm_capable = True # Disk logger.debug("Get disk information...") stdout, stderr, exitstatus = conn.execute('hwinfo --disk') for line in stdout: line = line.strip() if line.startswith('Size:'): machine_.disk_primary_size = int( int(line.split()[1]) / 2 / 1024**2) elif line.startswith('Attached to:'): opening_bracket = line.find('(') closing_bracket = line.find(')') if opening_bracket > 0 and closing_bracket > 0: machine_.disk_type = line[opening_bracket + 1:closing_bracket] else: machine_.disk_type = 'Unknown disk type' break # lsmod logger.debug("Get 'lsmod'...") stdout, stderr, exitstatus = conn.execute('lsmod') machine_.lsmod = normalize_ascii("".join(stdout)) # lspci logger.debug("Get 'lspci'...") stdout, stderr, exitstatus = conn.execute('lspci -vvv -nn') machine_.lspci = normalize_ascii("".join(stdout)) # last logger.debug("Get 'last'...") output, stderr, exitstatus = conn.execute( 'last | grep -v reboot | head -n 1') string = ''.join(output) result = string[0:8] + string[38:49] machine_.last = normalize_ascii("".join(result)) # hwinfo logger.debug("Get 'hwinfo' (full)...") stdout, stderr, exitstatus = conn.execute( 'hwinfo --bios ' + '--block --bridge --cdrom --cpu --disk --floppy --framebuffer ' + '--gfxcard --hub --ide --isapnp --isdn --keyboard --memory ' + '--monitor --mouse --netcard --network --partition --pci --pcmcia ' + '--scsi --smp --sound --sys --tape --tv --usb --usb-ctrl --wlan') machine_.hwinfo = normalize_ascii("".join(stdout)) # dmidecode logger.debug("Get 'dmidecode'...") stdout, stderr, exitstatus = conn.execute('dmidecode') machine_.dmidecode = normalize_ascii("".join(stdout)) # dmesg logger.debug("Get 'dmesg'...") stdout, stderr, exitstatus = conn.execute( 'if [ -e /var/log/boot.msg ]; then ' + 'cat /var/log/boot.msg; else journalctl -xl | head -n200; ' + 'fi') machine_.dmesg = normalize_ascii("".join(stdout)) # lsscsi logger.debug("Get 'lsscsi'...") stdout, stderr, exitstatus = conn.execute('lsscsi -s') machine_.lsscsi = normalize_ascii("".join(stdout)) # lsusb logger.debug("Get 'lsusb'...") stdout, stderr, exitstatus = conn.execute('lsusb') machine_.lsusb = normalize_ascii("".join(stdout)) # IPMI logger.debug("Check for IPMI...") machine_.ipmi = machine_.dmidecode.find('IPMI') >= 0 # Firmware script logger.debug("Get BIOS version...") output, stderr, exitstatus = conn.execute_script_remote( 'machine_get_firmware.sh') if output and output[0]: machine_.bios_version = output[0].strip() return machine_ except Exception as e: logger.error("{} ({})".format(fqdn, e)) return False finally: if conn: conn.close() if timer: timer.cancel() return None
def get_setup_records(self, architecture, machinegroup=None, grouped=True, delimiter=':'): """ Collect domain and architecture or machine group specific setup records. Each domain has one optional TFTP server providing available records for machine setup. If `grouped` is False, a list of all records gets returned (no grouping). Expects stdout: ['DISTRIBUTION-<architecture|machinegroup>-FLAVOUR', ...] [ 'SLES12-SP3-x86_64-install\n', 'SLES12-SP3-x86_64-install-ssh\n', ... 'SLES12-SP2-x86_64-install\n', 'SLES12-SP2-x86_64-rescue\n', ..., 'local-x86_64\n', ..., ] Returns (grouped is `True`): OrderedDict([ ('SLES12-SP3', [ 'SLES12-SP3-x86_64-install', 'SLES12-SP3-x86_64-install-ssh', ... ]), ('SLES12-SP2', [ 'SLES12-SP2-x86_64-install', 'SLES12-SP2-x86_64-rescue', ... ]), ('local', [ 'local-x86_64' ]) ) Returns (grouped is `False`): [ 'SLES12-SP3-x86_64-install', 'SLES12-SP3-x86_64-install-ssh', ... 'SLES12-SP2-x86_64-install', 'SLES12-SP2-x86_64-rescue', ..., 'local-x86_64', ..., ] """ from utils.ssh import SSH def grouping(records): """ Group records for HTML form. """ groups = {} for record in records: record_ = record.split(delimiter) if len(record_) == 2: prefix = record_[0] suffix = record_[1] else: logger.debug( "Setup record has invalid format: '{}'".format(record)) continue if prefix not in groups: groups[prefix] = [] groups[prefix].append(suffix) return collections.OrderedDict(sorted(groups.items())) if not self.tftp_server: logger.warning("No TFTP server available for '{}'".format( self.name)) return {} list_command_template = ServerConfig.objects.by_key( 'setup.list.command') context = Context({ 'architecture': architecture, 'machinegroup': machinegroup }) list_command = Template(list_command_template).render(context) try: conn = SSH(self.tftp_server.fqdn) conn.connect() logger.debug("Fetch setup records: {}:{}".format( self.tftp_server.fqdn, list_command)) stdout, stderr, exitstatus = conn.execute(list_command) conn.close() if exitstatus != 0: logger.warning(str(stderr)) return {} logger.debug("Found {} setup records on {}".format( len(stdout), self.tftp_server.fqdn)) except Exception as e: logger.warning("Couldn't fetch record list for setup: {}".format( str(e))) return {} finally: if conn: conn.close() records = list(map(lambda record: record.strip('\n'), stdout)) if grouped: records = grouping(records) else: records = list( map(lambda record: record.split(delimiter)[1], records)) return records
def start_decrypt_app(host, port, bundle_id, output_ipa, verbose=False): def log_verbose(message): if verbose: log_purple('[VERBOSE] {}'.format(message)) def shell(command): p = subprocess.run(command.split(), capture_output=True, text=True) return p.stdout, p.stderr # ------------------- Start --------------------- start = timer() def clean_tmp_files(): _, _ = ssh.execute('rm -rf /var/tmp/*.app') _, _ = ssh.execute('rm -rf /var/tmp/Payload') _, _ = ssh.execute('rm -rf /var/tmp/file.ipa') # --------- Locate app ---------------- log_yellow('Locating app...') ssh = SSH(host, port, 'root') clean_tmp_files() base_path = '/var/containers/Bundle/Application/' _, apps = ssh.execute('ls {}'.format(base_path)) apps = [x for x in apps.split('\n')[::-1] if x] log_verbose('[DEVICE] Apps folders: {}'.format(apps)) correct_folder = '' for app in apps: code, _ = ssh.execute('cat {}{}/iTunesMetadata.plist | grep {}'.format( base_path, app, bundle_id), verbose=verbose) if code == 0: correct_folder = app break if correct_folder == '': log_red('[DEVICE] Error! Could not find app folder. Aborting...') return 1 log_verbose('[DEVICE] Found folder: {}'.format(correct_folder)) # --------- Start decryption ---------------- log_yellow('Decrypting app...') # Copy .app to /var/tmp/ log_verbose('Copying .app to /var/tmp/') app_path = '/var/containers/Bundle/Application/{}/*.app'.format( correct_folder) code, _ = ssh.execute('cp -r {} /var/tmp'.format(app_path), verbose=verbose) if code != 0: log_red('Error! Could not copy .app. Aborting...') return 1 def decrypt(binary_name, binary_folder, output): log_yellow('Decrypting binary {}'.format(binary_name)) binary_name = binary_name.replace(' ', '\ ') output = output.replace(' ', '\ ') cmd = 'flexdecrypt {}/{} --output {}'.format(binary_folder, binary_name, output) code, o = ssh.execute(cmd, timeout=30, verbose=verbose) if code != 0 or 'Wrote decrypted image' not in o: log_red( 'Error! Failed to decrypt {}. Aborting...'.format(binary_name)) return 1 # chmod decrypted binary ssh.execute('chmod +x {}'.format(output), verbose=verbose) # Main binary code, main_binary_name = ssh.execute( 'plutil -key CFBundleExecutable {}/Info.plist'.format(app_path), verbose=verbose) main_binary_name = main_binary_name.strip() if code != 0 or main_binary_name == '': log_red('Error! Could not retrieve main binary name. Aborting...') return 1 log_verbose('Main binary: {}'.format(main_binary_name)) decrypt(main_binary_name, app_path, '/var/tmp/*.app/{}'.format(main_binary_name)) # Plugin binaries _, plugins_output = ssh.execute('ls {}/PlugIns'.format(app_path), verbose=verbose) plugins = [x for x in plugins_output.split('\n') if x] log_verbose('Plugins: {}'.format(plugins)) for plugin in plugins: _, plugin_binary_name = ssh.execute( 'plutil -key CFBundleExecutable {}/PlugIns/{}/Info.plist'.format( app_path, plugin), verbose=verbose) plugin_binary_name = plugin_binary_name.strip() if plugin_binary_name != '': decrypt( plugin_binary_name, '{}/PlugIns/{}'.format(app_path, plugin), '/var/tmp/*.app/PlugIns/{p}/{b}'.format(p=plugin, b=plugin_binary_name)) # Framework binaries _, frameworks_output = ssh.execute('ls {}/Frameworks'.format(app_path), verbose=verbose) frameworks = [ x for x in frameworks_output.split('\n') if x.endswith('.framework') ] log_verbose('Frameworks: {}'.format(frameworks)) for framework in frameworks: _, framework_binary_name = ssh.execute( 'plutil -key CFBundleExecutable {}/Frameworks/{}/Info.plist'. format(app_path, framework), verbose=verbose) framework_binary_name = framework_binary_name.strip() if framework_binary_name != '': decrypt( framework_binary_name, '{}/Frameworks/{}'.format(app_path, framework), '/var/tmp/*.app/Frameworks/{f}/{b}'.format( f=framework, b=framework_binary_name)) log_green('Decryption done!') time.sleep(1) # ----------- Remove _CodeSignature, PkgInfo, SC_Info, Watch ------------ log_yellow('Removing unneeded files...') _, _ = ssh.execute('rm -rf /var/tmp/*.app/_CodeSignature') _, _ = ssh.execute('rm -rf /var/tmp/*.app/PkgInfo') _, _ = ssh.execute('rm -rf /var/tmp/*.app/SC_Info') _, _ = ssh.execute('rm -rf /var/tmp/*.app/Watch') # ----------- Disable NSApplicationRequiresArcade flag ------------ log_yellow('Setting NSApplicationRequiresArcade flag to NO, if any...') _, _ = ssh.execute( 'plutil -no -key NSApplicationRequiresArcade /var/tmp/*.app/Info.plist' ) # ----------------- Add credits file ----------------------- log_yellow('Adding credits file...') date = datetime.now().strftime("%d/%m/%Y %H:%M:%S") _, _ = ssh.execute( "cd /var/tmp/*.app && echo '{} - Cracked exclusively for appdb.to using CrackBot3' > appdb" .format(date)) # --------- Zip as .ipa ---------------- log_yellow('Zipping as .ipa...') _, _ = ssh.execute( 'mkdir -p /var/tmp/Payload && mv /var/tmp/*.app /var/tmp/Payload') _, _ = ssh.execute('cd /var/tmp && zip -qrF file.ipa Payload') _, _ = ssh.execute('rm -rf /var/tmp/Payload') # --------- Transfer .ipa ---------------- log_yellow('Done, transfering ipa...') _, _ = shell('scp -p -P {} root@{}:/var/tmp/file.ipa {}'.format( port, host, output_ipa)) # --------- Done! ---------------- end = timer() clean_tmp_files() log_green('Done! Script ended in {}'.format( str(timedelta(seconds=int(end - start))))) return 0