__init__.py000064400000000260147205360270006661 0ustar00from pathlib import Path SCRIPT_BASEPATH = Path(__file__).parent.resolve() POWERSHELL_PATH = SCRIPT_BASEPATH / 'powershell' SHELL_SCRIPT_PATH = SCRIPT_BASEPATH / 'resources' linux.py000064400000272156147205360270006300 0ustar00# pylint: disable-msg=C0302 # To be addressed in https://jira.godaddy.com/browse/HPLAT-3186 import base64 import functools import glob import grp import json import logging import os import pwd import re import shlex import shutil import subprocess from datetime import datetime from enum import Enum from pathlib import Path from typing import Optional, Tuple, Union, List, Dict, Any from collections import OrderedDict from shutil import rmtree from cryptography.x509 import load_pem_x509_certificate from importlib_resources import read_text from customer_local_ops import Ops, OpType, NydusResult from customer_local_ops.exceptions import DecryptError from customer_local_ops.operating_system import SHELL_SCRIPT_PATH from customer_local_ops.operating_system.package_manager import Apt, AptEOL, PackageManager, Yum from customer_local_ops.util.execute import runCommand, RunCommandResult, run_command_pipe, run_shell_script_file from customer_local_ops.util.helpers import append_line, edit_file_lines, replace_line, create_file from customer_local_ops.util.retry import retry, Retry, RETRY # `unused` params are an artifact of Archon workflows requiring an I/O chain for sequencing. LOG = logging.getLogger(__name__) NYDUS_EXECUTOR_CRT_PATH = os.path.join(os.path.sep, 'opt', 'nydus', 'ssl', 'executor.crt') SUDOERS_PREFIX = os.path.join(os.path.sep, 'etc', 'sudoers.d') SYSTEM_USERS = 'nydus', '48-wp-toolkit' EXEMPT_SUDOERS_PREFIX = ('icinga', ) HOSTS_PATH = os.path.join(os.path.sep, 'etc', 'hosts') CLOUD_CONFIG_PATH = os.path.join(os.path.sep, 'etc', 'cloud', 'cloud.cfg') CLOUD_CONFIG_PRESERVE_HOSTNAME_PATH = os.path.join(os.path.sep, 'etc', 'cloud', 'cloud.cfg.d', '50_preserve_hostname.cfg') CLOUD_CONFIG_UPDATE_HOSTS_INIT_MODULE = 'update_etc_hosts' CENTOS6_YUM_REPO = '/etc/yum.repos.d/CentOS-Base.repo' HFS_COMMON_YUM_REPO = '/etc/yum.repos.d/hfs-common.repo' MAX_FILE_SIZE = 65536 SEMVER_RGX = re.compile( r'^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\-(?P0|[1-9]\d*)$') MAJOR_ONLY_RGX = re.compile(r'^(?P0|[1-9]\d*)$') LINUX_OS_INFO_FILE = "/etc/os-release" VPS4_ALLOW_DELETE_PREFIXES = ['Xdjw6fGvTto3MktG'] # Type aliases RetryInstallCommandResult = Union[Retry, RunCommandResult] class NydusUpdateType(str, Enum): UPGRADE = 'upgrade' DOWNGRADE = 'downgrade' REINSTALL = 'reinstall' class UnsupportedUnit(Exception): def __init__(self, fields, supported_units): super().__init__( 'Unsupported unit in fields %s. Supported units: %s' % ( fields, supported_units)) def add_sudoer(username): path = os.path.join(SUDOERS_PREFIX, username) try: create_file(path, "%s ALL=(ALL) NOPASSWD: ALL\n" % username) os.chmod(path, 0o0440) except (OSError, IOError): message = "failed to write sudoers file for %s (%s)" % (username, path) LOG.exception(message) raise return path def remove_sudoer(username: str) -> str: """ Remove sudoer permissions from a user. :param username: The username :raises: OSError, IOError if the path can't be deleted :return: The path to the sudoer file """ if username in SYSTEM_USERS: raise ValueError('Cannot remove sudo for system user "%s"' % username) path = os.path.join(SUDOERS_PREFIX, username) try: if os.path.exists(path): os.unlink(path) except (OSError, IOError): message = 'failed to remove sudoers file for %s (%s)' % (username, path) LOG.exception(message) raise return path def remove_sudoers(): filelist = [f for f in os.listdir(SUDOERS_PREFIX) if f not in SYSTEM_USERS and not f.startswith(EXEMPT_SUDOERS_PREFIX)] for f in filelist: remove_sudoer(f) def shutdown(): try: LOG.info('shutdown VM') os.system('shutdown -h now') except (OSError, IOError) as ex: LOG.exception("failed to shutdown vm. error: '%s'", str(ex)) raise def set_safe_env(): env = dict(os.environ) lp_key = 'LD_LIBRARY_PATH' lp_orig = env.get(lp_key + '_ORIG') if lp_orig is not None: env[lp_key] = lp_orig else: env.pop(lp_key, None) return env class Linux(Ops): DISK_UTILIZATION_PATH = '/' MEMORY_UTILIZATION_SUPPORTED_UNITS = frozenset(['kB']) RETRYABLE_INSTALL_ERRORS = ['Could not resolve host', 'Could not get lock', 'has no installation candidate', 'cannot sync correctly', 'Error performing handshake'] PANOPTA_MANIFEST_FILE = '/etc/fm-agent-manifest' PANOPTA_CONFIG_FILE = '/etc/fm-agent/fm_agent.cfg' PANOPTA_AGENT_NAME = 'fm-agent' PANOPTA_YUM_REPO = '/etc/yum.repos.d/fortimonitor.repo' PANOPTA_YUM_REPO_TEMPLATE = 'fortimonitor.repo' SYSTEMCTL = '/usr/bin/systemctl' QEMU_AGENT_CONFIG_LOC = "/etc/sysconfig/qemu-ga" op_type = OpType.OPERATING_SYSTEM package_manager = None # type: PackageManager def __init__(self, package_manager: Optional[PackageManager] = None) -> None: super().__init__() self.package_manager = package_manager def _get_vm_tag(self): """Return this VM's tag.""" tag = None with open(NYDUS_EXECUTOR_CRT_PATH, "rb") as cert_file: cert_contents = cert_file.read() cert = load_pem_x509_certificate(cert_contents) subject = cert.subject.rfc4514_string() subject_list = subject.split(",") for item in subject_list: if item.startswith('CN='): tag = item.split("=")[1].strip() break if tag is not None: return 0, tag, '' return 1, '', 'Unable to retrieve vm tag' def _install(self, *packages: str, **kwargs) -> RetryInstallCommandResult: """Install packages with operating system's package manager. :param *packages: specifications of one or more packages to install :returns: - a Retry object if a retryable error occurred, or - result of last command ran; execution stops on first non-zero exit code :raises ValueError: when no packages are specified """ if not packages: raise ValueError('At least one package is required') commands = OrderedDict() skip_update_indices = kwargs.get('skip_update_indices', False) if not skip_update_indices: commands['update_indices'] = self.package_manager.update_indices commands['install'] = lambda: self.package_manager.install(*packages) for method, command in commands.items(): exit_code, outs, errs = command() if any((e in errs) for e in self.RETRYABLE_INSTALL_ERRORS): LOG.warning('Temporary error, will retry: %s', errs) return RETRY if exit_code != 0: if method != 'update_indices': break return exit_code, outs, errs def add_user(self, payload, unused=None): op_name = 'add_user' username = payload['username'] fail_if_exists = payload.get('fail_if_exists', True) # Default for backwards compatibility exit_code, outs, errs = runCommand( ['getent', 'passwd', username], 'does_user_exist', errorOK=True) if exit_code == 0: if fail_if_exists: LOG.error('User %s already exists', username) return False, self.build_result_dict(outs, errs, op_name) else: exit_code, outs, errs = self._run_add_user_command(username) if exit_code != 0: LOG.error('failed to add user: %s (%s)', outs, errs) return False, self.build_result_dict(outs, errs, op_name) return self._change_password(payload, op_name) def _run_add_user_command(self, username: str) -> Tuple[int, str, str]: """ Run the add user command (differs per linux flavor) :param username: The username :return: exit code, output, errors """ return runCommand(['useradd', '-m', username], 'add_user') def remove_user(self, username, unused=None) -> NydusResult: """ Remove the user from the server :param username: The username :param unused: Parameter used for workflow chaining :return: Nydus Result Dict """ op_name = 'remove_user' # running `pkill` is only a precaution from `userdel` reporting error when user is logged in runCommand("pkill -u %s" % username, 'remove_user', useShell=True) exit_code, outs, errs = runCommand( "userdel -fr %s" % username, 'remove_user', useShell=True) if exit_code != 0: LOG.error("failed to remove user: %s (%s)", outs, errs) return False, self.build_result_dict(outs, errs, op_name) try: remove_sudoer(username) except (OSError, IOError) as ex: LOG.error("failed to remove sudoer: %s (%s)", '', str(ex)) return False, self.build_result_dict('', str(ex), op_name) return self.build_result_dict(outs, errs, op_name) def _change_password(self, payload, op_name): username = payload['username'] encrypted_password = payload['encrypted_password'] try: password = self.decrypt(encrypted_password) except DecryptError as ex: return False, self.build_result_dict(ex.outs, ex.errs, op_name) exit_code, outs, errs = self._run_change_password_cmd(username, password, op_name) if exit_code != 0: LOG.error("failed to change password: %s (%s)", outs, errs) return False, self.build_result_dict(outs, errs, op_name) return self.build_result_dict(outs, errs, op_name) def _run_change_password_cmd(self, username: str, password: str, op_name: str) -> Tuple[int, str, str]: """ Run the change password command :param username: The Username :param password: The user password :param op_name: The name of the calling op :return: The change password command """ chpasswd_arg = shlex.quote('%s:%s' % (username, password)) cmd = "echo %s | chpasswd" % chpasswd_arg return runCommand(cmd, op_name, useShell=True, omitString=chpasswd_arg) def change_password(self, payload, unused=None): return self._change_password(payload, 'change_password') def enable_admin(self, username, unused=None): errs = outs = '' op_name = 'enable_admin' try: outs = add_sudoer(username) except (OSError, IOError) as ex: LOG.exception('%s: failed to enable admin for user %s: %s', op_name, username, str(ex)) errs = str(ex) return False, self.build_result_dict(outs, errs, op_name) LOG.info('%s: Enabled admin for user %s', op_name, username) return self.build_result_dict(outs, errs, op_name) def disable_all_admins(self, unused=None): op_name = 'disable_all_admins' LOG.info('Removing user sudo permissions for * at %s', SUDOERS_PREFIX) remove_sudoers() admin_account_name = 'temphfsadmin' try: exit_code, stdout, stderr = self.remove_account(admin_account_name, op_name) result_dict = self.build_result_dict(stdout, stderr, op_name) if exit_code != 0: return False, result_dict except Exception: # pylint: disable=broad-except LOG.exception("%s: error removing '%s' account", op_name, admin_account_name) raise return result_dict def account_exists(self, account_name: str) -> bool: """\ Check to see if the specified account exists. :param account_name: The name of the account for which to check :returns: True if the account exists; otherwise, false """ try: pwd.getpwnam(account_name) except KeyError: return False return True def remove_account(self, account_name: str, op_name: Optional[str] = None) -> Tuple[int, str, str]: """\ Remove the specified account, if it exists :param account_name: The name of the account to remove :param op_name: Optional string indicating the name of the parent op. If not specified, it will default to `"remove_account"` :returns: A 3-tuple containing an integer indicating the success or failure of the overall operation and two strings containing stdout and stderr from the most recently executed subprocess """ if op_name is None: op_name = 'remove_account' if not self.account_exists(account_name): return 0, "", "" return runCommand(['/usr/sbin/userdel', '--force', '--remove', account_name], tag=op_name) def remove_non_default_users(self): allowedUsers = ['_apt', 'abrt', 'adm', 'apache', 'avahi', 'avahi-autoipd', 'backup', 'bin', 'bind', 'cpanel', 'cpanelcabcache', 'cpanelconnecttrack', 'cpaneleximfilter', 'cpaneleximscanner', 'cpanellogin', 'cpanelphpmyadmin', 'cpanelphppgadmin', 'cpanelroundcube', 'cpanelrrdtool', 'cpses', 'daemon', 'dbus', 'Debian-exim', 'dovecot', 'dovenull', 'ftp', 'games', 'gnats', 'gopher', 'haldaemon', 'halt', 'horde_sysuser', 'irc', 'list', 'lp', 'mail', 'mailman', 'mailnull', 'man', 'messagebus', 'mhandlers-user', 'mysql', 'named', 'news', 'nginx', 'nobody', 'nydus', 'nscd', 'ntp', 'operator', 'polkitd', 'popuser', 'postfix', 'proxy', 'psaadm', 'psaftp', 'roundcube_sysuser', 'root', 'rpc', 'saslauth', 'shutdown', 'smmsp', 'smmta', 'sshd', 'statd', 'sw-cp-server', 'sync', 'sys', 'syslog', 'systemd-bus-proxy', 'systemd-network', 'systemd-resolve', 'systemd-timesync', 'tcpdump', 'tss', 'uucp', 'uuidd', 'vcsa', 'webalizer', 'www-data'] allowedUsers.extend(SYSTEM_USERS) exit_code, outs, errs = runCommand( ['cut', '-d:', '-f1', '/etc/passwd'], 'get current users') user_list = outs.split('\n') LOG.debug("exit_code: %s currentUserList: %s errs: %s", exit_code, user_list, errs) for user in user_list: if user not in allowedUsers: LOG.info("Removing user: %s", user) self.remove_user(user) def disable_admin(self, username, unused=None): LOG.info('Removing user sudo permissions for %s at %s', username, SUDOERS_PREFIX) remove_sudoer(username) def shutdown_clean(self, unused=None): shutdown() RETRY_CONFIGURE_MTA_TIMEOUT = 1200 # seconds # pylint: disable=invalid-name RETRY_CONFIGURE_MTA_RETRY_INTERVAL = 30 # seconds # pylint: disable=invalid-name @retry(interval=RETRY_CONFIGURE_MTA_RETRY_INTERVAL, timeout=RETRY_CONFIGURE_MTA_TIMEOUT) def configure_mta(self, payload, unused=None, intermediate_result=None): # Function is split in order that control panel ops can call underlying os function directly, # without incurring op overhead such as formatted retry results op_name = 'configure_mta' return self.do_configure_mta(payload, op_name, intermediate_result=intermediate_result) def do_configure_mta(self, payload, op_name, intermediate_result: Dict[str, Any] = None): relay = payload.get('relay_address') LOG.debug("%s %s start relayAddress: %s", self.get_op_type().value, op_name, relay) install_result = self._install('sendmail') if isinstance(install_result, Retry): return install_result exit_code, outs, errs = install_result if exit_code != 0: LOG.error('Failed installing sendmail!\n%s', outs + '\n' + errs) self.list_processes() return False, self.build_result_dict(outs, errs, op_name) try: self.configure_sendmail(relay) except Exception as ex: # pylint: disable=broad-except LOG.error('os_op configure_mta result(fail):' + str(ex) + '\n' + outs + '\n' + errs) return False, self.build_result_dict(outs, errs, op_name) return self.build_result_dict(outs, errs, op_name) def configure_sendmail(self, relay: str = None) -> None: """Configure Sendmail. :param relay: mail relay to set """ if relay is None: return set_tgt = functools.partial(replace_line, match='DS', replace='DS[%s]\n' % relay, firstword=False) edit_file_lines('/etc/mail/sendmail.cf', set_tgt) edit_file_lines('/etc/mail/submit.cf', set_tgt) def list_processes(self): LOG.error( 'Process list:\n%s', runCommand('ps -ef'.split(), 'list processes')) def change_hostname(self, payload, unused=None): op_name = 'change_hostname' exit_code, outs, errs = runCommand( "hostnamectl set-hostname %s" % payload['hostname'], 'changeHostname', useShell=True) if exit_code != 0: return False, self.build_result_dict(outs, errs, op_name) exit_code, outs, errs = self.update_etc_hosts_hostname( payload['hostname'], payload['ip_address'], op_name) return exit_code == 0, self.build_result_dict(outs, errs, op_name) def get_ip_regex(self, ip_addr: str) -> str: """Converts an ipv4 ip address into a regex compatible form for match/replace functions :param ip_addr: The ipv4 IP address """ ip_addr_parts = ip_addr.split('.') ip_regex = r'\.'.join(ip_addr_parts) return ip_regex def get_hostname_prefix(self, hostname: str) -> str: """Returns the first part of a fully-qualified hostname (before the first '.') or the entire hostname if there are no '.' chars :param hostname: The Server hostname """ hostname_parts = hostname.split('.') hostname_prefix = hostname_parts[0] return hostname_prefix def regex_replace(self, path: str, regex: str, tag: str = None) -> Tuple[int, str, str]: """Updates file entries using the regex replacement string :param path: The path to the file :param regex: The regex replacement string for sed in format {regex_match}/{regex_replace} :param tag: Optional tag to describe operation """ if tag is None: tag = 'regex_replace' command = "sed -i -r 's/{regex}/' {path}".format( regex=regex, path=path) return runCommand(command, tag, useShell=True) def update_etc_hosts_hostname(self, hostname: str, ip_addr: str, op_name: str) -> Tuple[int, str, str]: """Updates the /etc/hosts file with the new hostname for the server. Need to use Child class methods. :param hostname: The new server hostname :param ip_addr: The IP address for the server :param op_name: The op that is running this function """ exit_code, outs, errs = self.disable_cloud_init_hosts_update() if exit_code != 0: return exit_code, outs, errs # ipv4 Fixed IP / Bound IP hostname_prefix = self.get_hostname_prefix(hostname) ip_regex = self.get_ip_regex(ip_addr) regex = r'^({ip_regex}).*$/\1 {hostname} {hostname_prefix}'.format( ip_regex=ip_regex, hostname=hostname, hostname_prefix=hostname_prefix) exit_code, outs, errs = self.regex_replace(HOSTS_PATH, regex, op_name) if exit_code != 0: return exit_code, outs, errs # ipv6 Fixed IP / Bound IP ip_regex = r"^([a-f0-9]{4}:[a-f0-9]{4}:[a-f0-9]{4}:[a-f0-9]{4}::).*$/\1" regex = r'{ip_regex} {hostname} {hostname_prefix}'.format( ip_regex=ip_regex, hostname=hostname, hostname_prefix=hostname_prefix) exit_code, outs, errs = self.regex_replace(HOSTS_PATH, regex, op_name) if exit_code != 0: return exit_code, outs, errs # ipv6 localhost local_ip = '::1' localhost = 'localhost' localhost_hosts = '(localhost.localdomain localhost6 localhost6.localdomain6|ip6-localhost ip6-loopback)' regex = r'^{local_ip}((\s+{localhost})*\s+{localhost_hosts}).*$/{local_ip}\1 {hostname}'.format( local_ip=local_ip, localhost=localhost, localhost_hosts=localhost_hosts, hostname=hostname) return self.regex_replace(HOSTS_PATH, regex, op_name) def disable_cloud_init_hosts_update(self) -> Tuple[int, str, str]: """If the server/vm is managed by cloud-init, this function comments out 'update_etc_hosts' from the list of modules that run in the 'init' stage of boot. If this isn't done, the /etc/hosts changes may not survive a reboot. """ if os.path.exists(CLOUD_CONFIG_PATH): regex = '^.*- {init_module}$/#- {init_module}'.format( init_module=CLOUD_CONFIG_UPDATE_HOSTS_INIT_MODULE) return self.regex_replace(CLOUD_CONFIG_PATH, regex, 'disable update_etc_hosts') return 0, 'cloud-init not installed', '' def enable_cloud_init_hosts_update(self) -> Tuple[int, str, str]: """If the server/vm is managed by cloud-init, this function removes the comment from 'update_etc_hosts' in the list of modules that run in the 'init' stage of boot. This enables the cloud init service to configure /etc/hosts on first boot """ if os.path.exists(CLOUD_CONFIG_PATH): regex = '^.*- {init_module}$/ - {init_module}'.format( init_module=CLOUD_CONFIG_UPDATE_HOSTS_INIT_MODULE) return self.regex_replace(CLOUD_CONFIG_PATH, regex, 'enable update_etc_hosts') return 0, 'cloud-init not installed', '' def get_os_info(self, *args: Any) -> Any: """Returns the contents of /etc/os-release file for the Operating System information""" op_name = "get_os_info" os_info = "" if os.path.exists(LINUX_OS_INFO_FILE): with open(LINUX_OS_INFO_FILE, 'r', encoding='utf-8') as os_info_file: contents = os_info_file.read() for line in contents.split('\n'): if line.startswith('ID='): os_info += "NAME={name}\n".format(name=line.split('=')[1].strip()) if line.startswith('VERSION_ID='): os_info += "VERSION={version}\n".format(version=line.split('=')[1].strip()) if os_info is not None: return os_info return False, self.build_result_dict('', 'Unable to retrieve OS information', op_name) def snapshot_clean(self, payload, unused=None): # pylint: disable=W0221 if not payload['clean']: return # TODO put all these commands into a single script # cannot use runCommand for this as it utilizes | command_line = "grep -qc set_hostname /etc/cloud/cloud.cfg || sudo sed -i 's/resizefs/&\\n - set_hostname\\n " \ "- update_hostname/' /etc/cloud/cloud.cfg" dummy_exitcode, o, e = run_command_pipe(command_line, useShell=True) LOG.info("re-enable cloud-init hostname updates. stdOut: %s stdErr: %s", o, e) # Newer style cloud-init hostname preservation # TODO: passing an array of args to runCommand does not escape # parameters following convention of subprocess module methods, # but it should. runCommand( "sed -i 's/^preserve_hostname:.*$/preserve_hostname: false/' /etc/cloud/cloud.cfg", 'Set cloud-init preserve_hostname = false', useShell=True, errorOK=True) try: os.unlink(CLOUD_CONFIG_PRESERVE_HOSTNAME_PATH) except FileNotFoundError: pass # Re-enable updating of /etc/hosts by cloud-init exit_code, outs, errs = self.enable_cloud_init_hosts_update() if exit_code != 0: return False, self.build_result_dict(outs, errs, 'snapshot_clean') # remove any non-default users self.remove_non_default_users() # Remove host keys used by the OpenSSH server so that unique ones will be # generated at VM startup time LOG.debug("Removing SSH host keys") runCommand("rm -f /etc/ssh/ssh_host_*", 'remove ssh host keys', useShell=True) # Remove DNS resolvers config file so on VM create the proper resolvers # associated with env will be set LOG.debug("Removing DNS resolvers") runCommand("> /etc/resolv.conf", 'remove dns config', useShell=True) # Delete all logs exit_code, outs, errs = runCommand( "rm -f /opt/thespian/director/thespian_system.log*", 'snapshotClean', useShell=True) return exit_code == 0, self.build_result_dict(outs, errs, 'snapshot_clean') def snapshot_prep(self, payload): return def _get_memory_utilization(self): """Get system memory utilization. We aim for our used calculation to match the "used" column of `free -m` as closely as possible. See used calculation for "Red Hat Enterprise Linux 7.1 or later" at https://access.redhat.com/solutions/406773. See also http://man7.org/linux/man-pages/man1/free.1.html#DESCRIPTION. """ command = 'cat /proc/meminfo |egrep "^(MemTotal|MemFree|Buffers|Cached|Slab):"' LOG.info('Collecting memory utilization: %s', command) result = subprocess.run( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines=True, timeout=3, check=True) # 'MemTotal: 4045332 kB\nMemFree: 2402732 kB\n...' fields = result.stdout.split() # ['MemTotal:', '4045864', 'kB', 'MemFree:', '2812676', 'kB', ...] if any([unit not in self.MEMORY_UTILIZATION_SUPPORTED_UNITS # pylint: disable=use-a-generator for unit in fields[2::3]]): raise UnsupportedUnit( fields, self.MEMORY_UTILIZATION_SUPPORTED_UNITS) fields = dict(zip( [f[:-1] for f in fields[::3]], # field name, strip colon [int(f) for f in fields[1::3]])) # value without unit # {'MemTotal': '4045864', 'MemFree': '2822160', ...} total = fields['MemTotal'] free = sum([v for f, v in fields.items() if f != 'MemTotal'] # pylint: disable=consider-using-generator ) return { 'memoryTotal': total >> 10, # KiB > MiB 'memoryUsed': (total - free) >> 10} def _get_cpu_utilization(self): command = 'sar -u 1 1'.split() LOG.info('Collecting cpu utilization: %s', command) result = subprocess.run( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, timeout=3, check=True) # Linux 3.10.0-862.14.4.el7.x86_64 (loclhfs01) 12/03/2018 _x86_64_ (2 CPU) # # 03:38:39 PM CPU %user %nice %system %iowait %steal %idle # 03:38:40 PM all 0.00 0.00 0.00 0.00 0.00 100.00 # Average: all 0.00 0.00 0.00 0.00 0.00 100.00 lines = result.stdout.split('\n') head = next(filter(lambda line: '%idle' in line, lines)) field_names = head.split() value_line = next(filter( lambda line: line.startswith('Average:'), lines)) values = value_line.split() values = dict(zip(reversed(field_names), reversed(values))) # Reversed because field 0 time is split into 2 values # {'%iowait': '0.00', '%nice': '0.00', '%idle': '100.00', 'PM': # 'Average:', '%steal': '0.00', 'CPU': 'all', '%user': '0.00', # '%system': '0.00'} return {'cpuUsed': 100.0 - float(values['%idle'])} def _yum_update(self): """Performs a yum update on the server""" return self._run_yum_command(['yum', 'update', '-y'], 'update system') def _yum_configure_repo(self) -> None: """Configures the yum repo. Is a noop for all linux distros except CentOS6, which is EOL""" return def _run_yum_command(self, *args, use_run_command_pipe: bool = False, **kwargs) -> Tuple[int, str, str]: """ A thin wrapper to runCommand or run_command_pipe for yum commands. Configures the yum repo before running the command :param use_run_command_pipe: Whether to use run_command_pipe instead of runCommand :return The runCommand/run_command_pipe result, as a tuple """ self._yum_configure_repo() if 'use_run_command_pipe' in kwargs: del kwargs['use_run_command_pipe'] if use_run_command_pipe: return run_command_pipe(*args, **kwargs) return runCommand(*args, **kwargs) def install_panopta(self, payload, *args, **kwargs): """Create a manifest file for the Panopta/Fortimonitor agent, add a repo file, and install panopta-agent/fm-agent using yum :param payload: payload containing customer_key (the key value to set in the manifest file) and template_ids (a csv string of the template ids to assign this server to) :return result of yum install operation""" self._create_panopta_manifest_file(payload) self._create_panopta_repo_file() exit_code, outs, errs = self._run_yum_command( ['yum', 'install', '-y', self.PANOPTA_AGENT_NAME], 'install {agent_name}'.format(agent_name=self.PANOPTA_AGENT_NAME)) check_msg = "Installation of {agent_name} had an error".format( agent_name=self.PANOPTA_AGENT_NAME) silent_yum_install_error = check_msg in outs success = exit_code == 0 and (not silent_yum_install_error) return success, self.build_result_dict(outs, errs, 'install_panopta') def upgrade_panopta(self, *args, customer_key: Optional[str] = None, **kwargs): # pylint: disable=too-many-locals """Deletes panopta apt keys and upgrades panopta to fortimonitor""" # cleaning panopta-agent keys exit_code, outs, errs = runCommand( ['apt-key', 'del', '61EE28720129F5F3'], 'upgrade_panopta') sources_file_panopta = '/etc/apt/sources.list' with open(sources_file_panopta, 'r', encoding='utf-8') as source_file: lines = source_file.read() with open(sources_file_panopta, 'w', encoding='utf-8') as source_file: for line in lines.split('\n'): if 'Addition to add panopta-agent' in line: pass if 'deb http://packages.panopta.com/deb stable main' in line: pass else: source_file.write(line + '\n') if self._check_panopta_installed(): if customer_key is None: try: customer_key = self._get_panopta_customer_key() except ValueError as ex: message = "customer_key not found" LOG.exception(message) return False, self.build_result_dict('customer_key not found', str(ex), 'upgrade_panopta') cmd = "echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections" runCommand(cmd, 'upgrade_panopta deb conf', useShell=True) cmd = 'curl -s https://repo.fortimonitor.com/install/linux/fm_agent_install.sh | ' \ 'bash /dev/stdin -c {customer_key} -x -y'.format(customer_key=customer_key) exit_code, outs, errs = runCommand(cmd.encode('utf-8'), 'curl upgrade_panopta', useShell=True) check_msg = "Installation of fm-agent had an error" silent_yum_install_error = check_msg in outs success = exit_code == 0 and (not silent_yum_install_error) return success, self.build_result_dict('fm_agent installed', errs, 'upgrade_panopta') LOG.info("Panopta-agent not Installed") return exit_code == 0, self.build_result_dict("Panopta-agent not Installed", "", 'upgrade_panopta') def _get_panopta_customer_key(self, *args, **kwargs): """ get the Panopta customer key from the agent manifest file and return it """ manifest_file = '/etc/panopta-agent-manifest' if not os.path.exists(manifest_file): raise ValueError('Panopta manifest file(/etc/panopta-agent-manifest) not found') cmd = r"""grep customer_key {manifest_file} | sed s/'customer_key\s*=\s*'//""".format( manifest_file=manifest_file) _, outs, _ = runCommand(cmd, 'get_customer_key', useShell=True) if outs is None: raise ValueError('Customer key not present in Panopta manifest file') return outs def _check_panopta_installed(self): _, outs, _ = self._run_yum_command( ['yum', 'list', 'installed', '|', 'grep panopta-agent'], 'check panopta installed', use_run_command_pipe=True) return outs def _create_panopta_repo_file(self): repo_file_contents = read_text( 'customer_local_ops.operating_system.resources', self.PANOPTA_YUM_REPO_TEMPLATE) create_file(self.PANOPTA_YUM_REPO, repo_file_contents) def delete_panopta(self, *args, **kwargs): """ Deletes panopta-agent/fm-agent from the server and removes the panopta-agent/fm-agent manifest file :return result of yum uninstall operation""" try: _, outs, _ = self._run_yum_command( ['yum', 'list', 'installed', '|', 'grep {agent_name}'.format( agent_name=self.PANOPTA_AGENT_NAME)], 'check {agent_name} installed'.format(agent_name=self.PANOPTA_AGENT_NAME), use_run_command_pipe=True) if outs: exit_code, outs, errs = self._run_yum_command( ['yum', 'remove', '-y', self.PANOPTA_AGENT_NAME], 'delete {agent_name}'.format(agent_name=self.PANOPTA_AGENT_NAME)) if exit_code != 0: LOG.error('failed to remove %s agent', self.PANOPTA_AGENT_NAME) return False, self.build_result_dict(outs, errs, 'delete_panopta') else: exit_code, outs, errs = 0, '{agent_name} agent is not installed'\ .format(agent_name=self.PANOPTA_AGENT_NAME), '' if os.path.exists(self.PANOPTA_MANIFEST_FILE): os.unlink(self.PANOPTA_MANIFEST_FILE) exit_code = 0 outs = outs + '\n{agent_name} agent manifest file removed'.format(agent_name=self.PANOPTA_AGENT_NAME) # trying to delete the panopta-agent if its installed, applicable for old boxes _, outs2, _ = self._run_yum_command( ['yum', 'list', 'installed', '|', 'grep panopta-agent'], 'check panopta-agent installed', use_run_command_pipe=True) if outs2: exit_code, outs, errs = self._run_yum_command( ['yum', 'remove', '-y', 'panopta-agent'], 'delete {agent_name}'.format(agent_name='panopta-agent')) if exit_code != 0: LOG.error('failed to remove panopta-agent') return False, self.build_result_dict(outs, errs, 'delete_panopta') else: exit_code, outs, errs = 0, 'panopta-agent is not installed', '' if os.path.exists('/etc/panopta-agent-manifest'): os.unlink('/etc/panopta-agent-manifest') exit_code = 0 outs = outs + '\npanopta-agent manifest file removed' except (OSError, IOError) as ex: message = "Failed to unlink Panopta/Fortimonitor manifest file" LOG.exception(message) return False, self.build_result_dict('', str(ex), 'delete_panopta') return exit_code == 0, self.build_result_dict(outs, errs, 'delete_panopta') def get_panopta_server_key(self, *args, **kwargs): """ get the Panopta/Fortimonitor server key from the agent configuration file and return it :return a dictionary of the outs and errors """ agent_conf = self.PANOPTA_CONFIG_FILE if not os.path.exists(agent_conf): raise ValueError('Panopta/Fortimonitor config file: {agent_conf} not found'.format(agent_conf=agent_conf)) cmd = r"""grep server_key {agent_conf} | sed s/'server_key\s*=\s*'//""".format( agent_conf=agent_conf) exit_code, outs, _ = runCommand(cmd, 'get_server_key', useShell=True) return exit_code == 0, {'outs': outs, 'append_info': False} def update_invalid_resolvers(self, valid_resolvers: List[str], invalid_resolvers: List[str], *args, **kwargs) -> NydusResult: """ If the server has any of the listed invalid resolvers, replace them with the valid resolvers. Otherwise, leave the resolvers as-is. Currently the script takes only two valid and two invalid resolvers for updating. :param valid_resolvers: A list of valid dns nameservers to be added to the server :param invalid_resolvers: A list of invalid dns nameservers to be removed from the server :return: a dictionary of the outs and errors """ resolvers_arg = invalid_resolvers resolvers_arg.extend(valid_resolvers) exit_code, outs, errs = run_shell_script_file( script_file=SHELL_SCRIPT_PATH / "update_dns_linux.sh", tag="update_dns_resolvers", script_file_args=resolvers_arg) if exit_code != 0: errs = errs + '; ' + 'exit_code:' + str(exit_code) return exit_code == 0, self.build_result_dict(outs, errs, 'update_invalid_resolvers') def __cleanup_on_write_error(self, file_path: Path) -> None: """ Helper method that performs cleanup for the 'write_out_file' op in-case of any errors :param file_path: The file path to the file/directory that was being written out by the op """ if file_path.exists(): if file_path.is_file(): try: file_path.unlink() except FileNotFoundError: pass else: file_path.rmdir() def __validate_write_file(self, op_name: str, name: str, location: str, user_name: str, group_name: str, contents: str, is_file: bool, exist_ok: bool) -> NydusResult: """ Helper method that performs validation for the 'write_out_file' op, before the file/directory is written out. :param op_name: Name of the op, should typically be 'write_out_file' :param name: Name of the file/directory to be created :param location: The directory/folder in which the file is to be created :param user_name: The user who will own the file/directory :param group: The group who will own the file/directory :param contents: The contents to be written out to the file :param is_file: Boolean parameter indicating if its a file or directory that is to be created :param exist_ok: Boolean parameter indicating if existence of the file is ok """ folder_loc = Path(location) if not folder_loc.exists(): errs = "Folder '{}' doesn't exist".format(location) LOG.exception("%s: %s", op_name, errs) return False, self.build_result_dict("", errs, op_name) file_path = folder_loc / name if file_path.exists() and not exist_ok: errs = "File '{}' already exists".format(str(file_path)) LOG.exception("%s: %s", op_name, errs) return False, self.build_result_dict("", errs, op_name) try: pwd.getpwnam(user_name) except KeyError: errs = "User '{}' doesn't exist".format(user_name) LOG.exception("%s: %s", op_name, errs) return False, self.build_result_dict("", errs, op_name) try: grp.getgrnam(group_name) except KeyError: errs = "Group '{}' doesn't exist".format(group_name) LOG.exception("%s: %s", op_name, errs) return False, self.build_result_dict("", errs, op_name) if is_file and len(contents) > MAX_FILE_SIZE: errs = "File contents exceeds size limit of {}".format(MAX_FILE_SIZE) LOG.exception("%s: %s", op_name, errs) return False, self.build_result_dict("", errs, op_name) return True, {} def __write_file(self, op_name: str, file_path: Path, contents: str) -> NydusResult: """ Helper method that handles writing out to a file for the 'write_out_file' op. :param op_name: Name of the op, should typically be 'write_out_file' :param file_path: File path of the file to be written :param contents: The contents to be written out to the file """ try: if len(contents) == 0: file_path.touch(exist_ok=True) else: decoded_contents = base64.b64decode(contents) file_path.write_bytes(decoded_contents) except (OSError, IOError) as ex: LOG.exception('%s: failed to write file %s: %s', op_name, str(file_path), str(ex)) errs = str(ex) return False, self.build_result_dict("", errs, op_name) return True, {} def __make_directory(self, op_name, file_path): """ Helper method that handles creating a directory for the 'write_out_file' op. :param op_name: Name of the op, should typically be 'write_out_file' :param file_path: File path of the directory to be created """ try: file_path.mkdir(exist_ok=True) except (OSError, IOError) as ex: LOG.exception('%s: failed to create directory %s: %s', op_name, str(file_path), str(ex)) errs = str(ex) return False, self.build_result_dict("", errs, op_name) return True, {} def __change_file_perms(self, op_name: str, file_path: Path, perms: str) -> NydusResult: """ Helper method that handles setting the file permissions for the 'write_out_file' op. :param op_name: Name of the op, should typically be 'write_out_file' :param file_path: File path of the directory to be created :param perms: Permissions for the new file """ try: file_path.chmod(int(perms, base=8)) except PermissionError as ex: self.__cleanup_on_write_error(file_path) LOG.exception('%s: failed to change file permissions %s: %s', op_name, str(file_path), str(ex)) errs = str(ex) return False, self.build_result_dict("", errs, op_name) return True, {} def __change_file_ownership(self, op_name: str, file_path: Path, user_name: str, group_name: str) -> NydusResult: """ Helper method that handles setting the file ownership for the 'write_out_file' op. :param op_name: Name of the op, should typically be 'write_out_file' :param file_path: File path of the directory to be created :param user_name: Owning user of the file :param group_name: Owning group of the file """ try: shutil.chown(str(file_path), user_name, group_name) except PermissionError as ex: self.__cleanup_on_write_error(file_path) LOG.exception('%s: failed to change file ownership %s: %s', op_name, str(file_path), str(ex)) errs = str(ex) return False, self.build_result_dict("", errs, op_name) return True, {} def write_out_file(self, name: str, location: str, perms: str, user_name: str, group_name: str, contents: str = b'', is_file: bool = True, exist_ok: bool = False) -> NydusResult: """ Op for writing out a regular file (with content) or creating a directory. Allows sets the file permissions and user/group ownership for the created file. :param name: Name of the file/directory to be created :param location: The directory/folder in which the file is to be created :param perms: The file permissons of the created file :param user_name: The user who will own the file/directory :param group_name: The group who will own the file/directory :param contents: The contents to be written out to the file :param is_file: Boolean parameter indicating if its a file or directory that is to be created :param exist_ok: Boolean parameter indicating if existence of the file is ok """ op_name = 'write_out_file' ok, result = self.__validate_write_file( op_name, name, location, user_name, group_name, contents, is_file, exist_ok) if not ok: return ok, result file_path = Path(location) / name ok, result = self.__write_file(op_name, file_path, contents) \ if is_file \ else self.__make_directory(op_name, file_path) if not ok: return ok, result ok, result = self.__change_file_perms(op_name, file_path, perms) if not ok: return ok, result ok, result = self.__change_file_ownership(op_name, file_path, user_name, group_name) if not ok: return ok, result return True, self.build_result_dict("", "", op_name) def delete_file(self, name: str, location: str, is_file: bool = True) -> NydusResult: """ Op for deleting a file or a directory. :param name: Name of the file/directory to be deleted :param location: The directory/folder in which the file is to be deleted :param is_file: Boolean parameter indicating if its a file or directory that is to be deleted """ op_name = 'delete_file' file_path = Path(location) / name if not any(name.startswith(prefix) for prefix in VPS4_ALLOW_DELETE_PREFIXES): raise RuntimeError("Invalid file/directory prefix") try: if not is_file: rmtree(file_path) else: os.remove(file_path) except (OSError, IOError, PermissionError, FileNotFoundError) as ex: raise RuntimeError( '{}: failed to delete file/directory {}: {}'.format(op_name, str(file_path), str(ex)) ) from ex return True, self.build_result_dict("", "", op_name) def restart_service(self, service_name: str, is_systemd: bool = True, should_block: bool = True) -> NydusResult: """ Op that restarts a service. Allows for restarting of a SysV service or a SystemD based service. :param service_name: Name of the service to be restarted :param is_systemd: Flag indicating if the service to be restarted is systemd unit or not :param should_block: Flag specific to systemd indicating if the we should wait for the completion of the command execution """ rc_tag = 'restart_service' if is_systemd: if should_block: exit_code, outs, errs = runCommand( [self.SYSTEMCTL, 'restart', service_name], rc_tag) else: exit_code, outs, errs = runCommand( [self.SYSTEMCTL, 'restart', '--no-block', service_name], rc_tag) else: exit_code, outs, errs = runCommand( ['/sbin/service', service_name, 'restart'], rc_tag) if exit_code != 0: LOG.error("failed to restart service: %s (%s)", outs, errs) return False, self.build_result_dict(outs, errs, rc_tag) return True, self.build_result_dict("", "", rc_tag) def _get_file_hash(self, path: str, hash_type: str) -> str: """ Get the hash for a given filepath and hash type, e.g. md5, sha1, sha256 :param path: The path of the file to be hashed :param hash_type: The type of hash to perform :return: The hashed file as a string :raises: RunTimeException if hashing fails """ command = hash_type + 'sum' exit_code, outs, errs = runCommand( [command, path], "{hash_type} file".format(hash_type=hash_type)) if exit_code != 0: LOG.error("Failed to %s hash file %s: %s (%s)", hash_type, path, outs, errs) raise RuntimeError("Failed to %s hash file %s: %s (%s)" % (hash_type, path, outs, errs)) # outputs 'hash filename' outs_list = outs.split(" ") return outs_list[0].strip() def _get_file_timestamps(self, path: str) -> Dict[str, Any]: """ Get the access, modify and change date and time of given file path :param path: The path of the file :return: Dict containing access, modify and change timestamps in format YYYY-MM-DD HH:MM:SS.xxxxxxxx TZ_OFFSET """ try: access_time = datetime.fromtimestamp(os.path.getatime(path)) modify_time = datetime.fromtimestamp(os.path.getmtime(path)) change_time = datetime.fromtimestamp(os.path.getctime(path)) file_timestamps = {"access": str(access_time), "modify": str(modify_time), "change": str(change_time)} return file_timestamps except OSError as ex: LOG.error("Failed to file timestamps %s: %s", path, str(ex)) raise def get_file_info(self, paths: str) -> Dict[str, Any]: """ Op for retrieving file info (if file exists) including the md5, sha-1 and sha-256 hashes, and access, change and modify dates :param paths: Comma-separated list of paths of files :return: Dict with filenames and their associated timestamps and hashes (if file present) """ out_list = [] paths_list = paths.split(",") for path in paths_list: path = path.strip() out_dict = {"fileName": path} file_loc = Path(path) if not file_loc.exists(): out_dict["exists"] = False out_dict["md5"] = None out_dict["sha1"] = None out_dict["sha256"] = None out_dict["access"] = None out_dict["modify"] = None out_dict["change"] = None else: out_dict["exists"] = True for hash_type in ['md5', 'sha1', 'sha256']: try: out_dict[hash_type] = self._get_file_hash(path, hash_type) except RuntimeError: # Set value to None if unable to get the hash out_dict[hash_type] = None try: file_timestamps = self._get_file_timestamps(path) out_dict["access"] = file_timestamps.get("access") out_dict["modify"] = file_timestamps.get("modify") out_dict["change"] = file_timestamps.get("change") except OSError: # Set value to None if unable to get the timestamps date out_dict["access"] = None out_dict["modify"] = None out_dict["change"] = None out_list.append(out_dict) ret = {"files": out_list} return ret def get_rpm_info(self) -> List[str]: """ Op for returning rpm info on the vm :return: List of installed rpms with additional info """ query_format_list = [ r'''"${HOSTNAME}|${VM_CONTAINER}|%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}|''', r'''%{INSTALLTIME}|%{BUILDHOST}|%{DSAHEADER:pgpsig}|%{RSAHEADER:pgpsig}|''', r'''%{SIGGPG:pgpsig}|%{SIGPGP:pgpsig}\n"'''] query_format = ''.join(query_format_list) cmd = "rpm -qa --queryformat " + query_format exit_code, outs, errs = run_command_pipe(cmd, useShell=True) if isinstance(outs, bytes): outs = outs.decode('utf-8') if isinstance(errs, bytes): errs = errs.decode('utf-8') if exit_code != 0: LOG.error("Failed to get rpm info: %s (%s)", outs, errs) return False, self.build_result_dict(outs, errs, 'get_rpm_info') ret = outs.split("\n") return ret RETRY_INSTALL_QEMU_AGENT_TIMEOUT = 1200 # pylint: disable=invalid-name RETRY_INSTALL_QEMU_AGENT_INTERVAL = 30 # pylint: disable=invalid-name @retry(interval=RETRY_INSTALL_QEMU_AGENT_INTERVAL, timeout=RETRY_INSTALL_QEMU_AGENT_TIMEOUT) def install_qemu_agent(self, pypi_url: str, *args, **kwargs) -> NydusResult: """ Op for installing the qemu agent on the vm :param pypi_url: The url for the pypi server where the package is located """ package_url = pypi_url + "/-/" + self.DISTRO + "/" + self.OS_VERSION + "/" + self.QEMU_PACKAGE_NAME result = self._install(package_url) if isinstance(result, Retry): return result exit_code, outs, errs = result if exit_code != 0: if 'Nothing to do' not in errs: return False, self.build_result_dict(outs, errs, 'install_qemu_agent') exit_code, outs, errs = self._configure_qemu_agent() if exit_code != 0: return False, self.build_result_dict(outs, errs, 'configure_qemu_agent') exit_code, outs, errs = self._enable_qemu_agent() if exit_code != 0: return False, self.build_result_dict(outs, errs, 'enable_qemu_agent') return def _configure_qemu_agent(self) -> RunCommandResult: """ Update the qemu agent configuration """ cmd = r"""sed -i 's/^BLACKLIST_RPC=.*$/BLACKLIST_RPC=""/g' """ + self.QEMU_AGENT_CONFIG_LOC return run_command_pipe(cmd, 'configure_qemu_agent') def _enable_qemu_agent(self) -> RunCommandResult: """ Enable the qemu guest agent to start at boot. """ return runCommand([self.SYSTEMCTL, 'enable', 'qemu-guest-agent'], 'enable_qemu_agent') class CentOS(Linux): DISTRO = 'Centos' def __init__(self): super().__init__(package_manager=Yum()) def _install(self, *packages: str, **kwargs) -> RetryInstallCommandResult: self._yum_configure_repo() return super()._install(*packages) def configure_ip_files(self, vm_address, addresses, gateway, unused=None): # NOQA pylint: disable=R0914,R0912 # NOQA added to avoid: # C901 'CentOS.configure_ip_files' is too complex (20). # backlog story created to refactor this to read data from mnt, instead of config. # additional story created to refactor this file to one file per OS/distribution. LOG.info("configure_ips: %s, %s, %s", vm_address, addresses, gateway) addresses.insert(0, vm_address) cfgdir = '/etc/sysconfig/network-scripts' for oldfile in glob.glob(os.path.join(cfgdir, 'ifcfg-eth0:*')): LOG.info('Removing old config: %s', oldfile) os.unlink(oldfile) for oldfile in glob.glob(os.path.join(cfgdir, 'route-eth0:*')): LOG.info('Removing old routing: %s', oldfile) os.unlink(oldfile) for oldfile in glob.glob(os.path.join(cfgdir, 'ifcfg-lo:*')): LOG.info('Removing old config: %s', oldfile) os.unlink(oldfile) for oldfile in glob.glob(os.path.join(cfgdir, 'route-lo:*')): LOG.info('Removing old routing: %s', oldfile) os.unlink(oldfile) nwconf = '/etc/sysconfig/network' if not os.path.exists(nwconf): create_file(nwconf, 'GATEWAYDEV=eth0\n') else: with open(nwconf, 'r', encoding='utf-8') as nwconf_file: gwdev = 'GATEWAYDEV' in nwconf_file.read() if gwdev: edit_file_lines(nwconf, functools.partial(replace_line, match='GATEWAYDEV', replace='GATEWAYDEV=eth0\n', firstword=False)) else: append_line(nwconf, 'GATEWAYDEV=eth0\n') with open(os.path.join(cfgdir, 'ifcfg-eth0'), 'r', encoding='utf-8') as base_cfg_file: base_cfg = base_cfg_file.read() if hasattr(base_cfg, 'decode'): base_cfg = base_cfg.decode('ascii') gw = gateway if not gw: for line in base_cfg.split('\n'): if line.startswith('GATEWAY='): gw = line[len('GATEWAY='):].split()[0] LOG.info('Endpoint Count: %s', str(len(addresses))) if len(addresses) < 2: def_route_line = 'DEFROUTE=yes\n' else: def_route_line = 'DEFROUTE=no\n' default_route_created = False for ifnum, ip_address in enumerate(addresses): # Disable Eth0 default route with open(os.path.join(cfgdir, 'ifcfg-eth0'), 'w', encoding='utf-8') as base_cfg_file: for line in base_cfg.split('\n'): if line.startswith('DEFROUTE'): base_cfg_file.write(def_route_line) else: base_cfg_file.write(line + '\n') if str(ip_address) in base_cfg: continue with open(os.path.join(cfgdir, 'ifcfg-eth0:%s' % ifnum), 'w', encoding='utf-8') as ethcfg: ethcfg.write('\n'.join(['DEVICE=eth0:%d' % ifnum, 'ONBOOT=yes', 'BOOTPROTO=static', 'IPADDR=' + str(ip_address), 'NETMASK=255.255.255.255']) + '\n') # remove any routes for this ip from route-eth0 (added by openstack) if os.path.exists(os.path.join(cfgdir, 'route-eth0')): with open(os.path.join(cfgdir, 'route-eth0'), 'r', encoding='utf-8') as base_route_file: base_route = base_route_file.read() with open(os.path.join(cfgdir, 'route-eth0'), 'w', encoding='utf-8') as base_route_file: for line in base_route.split('\n'): if str(ip_address) in line: pass else: base_route_file.write(line + '\n') # we only want to add one default route if default_route_created is not True: create_file(os.path.join(cfgdir, 'route-eth0:%s' % ifnum), 'default via %s dev eth0 proto static src %s metric 100\n' % (gw, str(ip_address))) LOG.info('Default route created for: %s', str(ip_address)) default_route_created = True LOG.warning('Configured %s to %s, gw %s', 'ifcg-eth0:%s' % ifnum, ip_address, gw) def update_etc_hosts_hostname(self, hostname: str, ip_addr: str, op_name: str) -> Tuple[int, str, str]: """Updates the /etc/hosts file with the new hostname for the server :param hostname: The new server hostname :param ip_addr: The IP address for the server :param op_name: The op that is running this function """ exit_code, outs, errs = super().update_etc_hosts_hostname(hostname, ip_addr, op_name) if exit_code != 0: return exit_code, outs, errs # ipv4 localhost comment = '#cloud-controlled; do not change' local_ip = '127.0.0.1' ip_regex = self.get_ip_regex(local_ip) regex = r'^{ip_regex}.*{comment}$/{local_ip} {hostname} {hostname} {comment}'.format( ip_regex=ip_regex, comment=comment, local_ip=local_ip, hostname=hostname) return self.regex_replace(HOSTS_PATH, regex, op_name) # pylint: disable=too-many-return-statements def update_nydus(self, pypi_url: str, update_type: Optional[str] = NydusUpdateType.REINSTALL, nydus_version: Optional[str] = None) -> NydusResult: """ Op for updating pyinstaller based nydus package :param pypi_url: The url for the pypi server where the package is located :param update_type: The type of update to perform, one of "reinstall", "upgrade" or "downgrade" :param nydus_version: The version of nydus to use for the update """ env = set_safe_env() LOG.info('update_type: %s , nydus_version: %s', update_type, nydus_version) if update_type not in (NydusUpdateType.REINSTALL, NydusUpdateType.DOWNGRADE, NydusUpdateType.UPGRADE): return False, self.build_result_dict("", "Invalid operation", 'update_nydus') exit_code, outs, errs = self._run_yum_command( ['yum', 'list', 'installed', 'nydus-executor'], 'Check for pyinstaller based package', env=env) if exit_code != 0: return False, self.build_result_dict( "", "Pyinstaller based nydus package is currently not installed", 'update_nydus') if update_type == NydusUpdateType.REINSTALL: exit_code, outs, errs = runCommand( ['rpm', '-q', '--queryformat', '%{VERSION}-%{RELEASE}', 'nydus-executor'], 'get nydus version', env=env) if exit_code != 0: return False, self.build_result_dict( "", "Error determining existing nydus version", 'update_nydus') # Force nydus version to be the same as the existing nydus version for reinstall nydus_version = outs if nydus_version is None: return False, self.build_result_dict( "", "Nydus version is a required parameter but wasn't provided", 'update_nydus') nydus_pkg_name = None if SEMVER_RGX.match(nydus_version): # eg: nydus-executor-6.21.1-97.x86_64.rpm nydus_pkg_name = 'nydus-executor-{}.x86_64.rpm'.format(nydus_version) elif MAJOR_ONLY_RGX.match(nydus_version): # eg: nydus-executor-6.rpm nydus_pkg_name = 'nydus-executor-{}.rpm'.format(nydus_version) else: return False, self.build_result_dict("", "Invalid nydus version", 'update_nydus') package_url = pypi_url + "/-/" + self.DISTRO + "/" + self.OS_VERSION + "/" + nydus_pkg_name exit_code = 0 if update_type == NydusUpdateType.UPGRADE: exit_code, outs, errs = self._run_yum_command( ['yum', 'update', '-y', package_url], 'Update nydus to version {}'.format(nydus_pkg_name), env=env) elif update_type == NydusUpdateType.REINSTALL: exit_code, outs, errs = self._run_yum_command( ['yum', 'reinstall', '-y', package_url], 'Update nydus to version {}'.format(nydus_pkg_name), env=env) elif update_type == NydusUpdateType.DOWNGRADE: exit_code, outs, errs = self._run_yum_command( ['yum', 'downgrade', '-y', package_url], 'Update nydus to version {}'.format(nydus_pkg_name), env=env) else: return False, self.build_result_dict( "", "Unsupported nydus update action", 'update_nydus') if exit_code != 0 or ("The same or higher version of nydus-executor is already installed" in errs): return False, self.build_result_dict("", "Nydus update failed {}".format(errs), 'update_nydus') exit_code, outs, errs = runCommand( ['systemctl', 'daemon-reload'], 'reload systemd unit files', env=env) if exit_code != 0: return False, self.build_result_dict( "", "Error reloading systemd unit files", 'get_nydus_version') return True, self.build_result_dict(outs, errs, 'update_nydus') def upgrade_panopta(self, *args, customer_key: Optional[str] = None, **kwargs) -> NydusResult: """Deletes panopta repo config and upgrades panopta to fortimonitor""" if not self._check_package_installed('panopta-agent'): return True, self.build_result_dict("", "Panopta is not installed, nothing to do", 'upgrade_panopta') if not self.install_python3(): return False, self.build_result_dict("", "Failed to install python3", 'upgrade_panopta') try: self._remove_panopta_repo() except (IOError, OSError) as ex: LOG.info("Error while deleting panopta repo file") return False, self.build_result_dict("", "Failed to delete panopta repo file: {}".format(str(ex)), 'upgrade_panopta') try: customer_key = self._get_panopta_customer_key() except ValueError as ex: message = "customer_key not found" LOG.exception(message) return False, self.build_result_dict('customer_key not found', str(ex), 'upgrade_panopta') cmd = r"""curl -s https://repo.fortimonitor.com/install/linux/fm_agent_install.sh | \ sudo bash /dev/stdin -c {customer_key} -x -y""".format(customer_key=customer_key) exit_code, outs, errs = runCommand(cmd, 'upgrade_panopta', useShell=True) if exit_code != 0: return False, self.build_result_dict(outs, errs, 'upgrade_panopta') err_check_msgs = ['Installation of fm-agent had an error', 'No suitable python version found', 'Agent installation must be run as root'] silent_yum_install_error = any(msg in outs for msg in err_check_msgs) success = exit_code == 0 and (not silent_yum_install_error) return success, self.build_result_dict('fm_agent installed', errs, 'upgrade_panopta') def _check_package_installed(self, package: str) -> bool: """Check if package is installed""" _, outs, _ = run_command_pipe('sudo yum list installed', 'Check whether package is installed') if package in outs.decode('utf-8'): return True return False def _remove_panopta_repo(self) -> None: """Remove panopta repo""" try: os.remove('/etc/yum.repos.d/panopta.repo') except FileNotFoundError: LOG.info("Panopta repo file not found") def install_python3(self) -> bool: """Install python3 if not already installed""" exit_code, outs, _ = runCommand(['python3', '-V'], 'Check Python version', useShell=True) if exit_code == 0 and 'Python 3.' in outs: return True try: self._add_hfs_common_repo() except (IOError, OSError) as ex: LOG.info("Failed to add hfs_common repo: %s", str(ex)) return False exit_code_yum, outs_yum, errs = self._run_yum_command( ['yum', 'install', '-y', 'python3'], 'Install python3') if exit_code_yum != 0: LOG.error("failed to install python3: %s (%s)", outs_yum, errs) return False try: self._remove_hfs_common_repo() except (IOError, OSError) as ex: LOG.info("Failed to remove hfs_common repo: %s", str(ex)) return False return True def _add_hfs_common_repo(self) -> None: """Add gpg key and hfs common repo config""" repo_file = HFS_COMMON_YUM_REPO repo_file_contents = read_text( 'customer_local_ops.operating_system.resources', 'hfs_common.repo') create_file(repo_file, repo_file_contents) def _remove_hfs_common_repo(self) -> None: """Remove hfs_common repo and gpg key""" os.remove(HFS_COMMON_YUM_REPO) class CentOS6(CentOS): OS_VERSION = '6' QEMU_PACKAGE_NAME = 'qemu-guest-agent-0.rpm' def update_nydus(self, *args, **kwargs) -> NydusResult: return False, self.build_result_dict("", "Not supported", 'update_nydus') def configure_ips(self, *args, **kwargs): super().configure_ip_files(*args, **kwargs) exit_code, outs, errs = runCommand( ['/sbin/service', 'network', 'restart'], 'network restart') if exit_code != 0: LOG.error("failed to configure ips: %s (%s)", outs, errs) return False, [outs, errs] return def change_hostname(self, payload, unused=None): # The hostname command is not persistent for CentOS-6 op_name = 'change_hostname' ip_addr = payload['ip_address'] hostname = payload['hostname'] exit_code, outs, errs = runCommand("hostname " + hostname, 'changeHostname', useShell=True) if exit_code != 0: return False, self.build_result_dict(outs, errs, op_name) # This is the persistent location for the hostname setting try: edit_file_lines( '/etc/sysconfig/network', functools.partial(replace_line, match='HOSTNAME', replace='HOSTNAME=%s\n' % hostname, firstword=False)) except Exception as ex: # pylint: disable=broad-except LOG.error("Failed to update /etc/sysconfig/network file: %s", str(ex)) return False, self.build_result_dict(outs, str(ex), op_name) # Ensure the hostname self-routes exit_code, outs, errs = self.update_etc_hosts_hostname(hostname, ip_addr, op_name) if exit_code != 0: return False, self.build_result_dict(outs, errs, op_name) return self.build_result_dict(outs, errs, op_name) def _yum_configure_repo(self) -> None: """Configures the yum repo. Is a noop for all linux distros except CentOS6, which is EOL""" yum_repo_file = CENTOS6_YUM_REPO repo_file_contents = read_text( 'customer_local_ops.operating_system.resources', 'centos6_base.repo') create_file(yum_repo_file, repo_file_contents) def restart_service(self, service_name: str, is_systemd: bool = False, should_block: bool = True) -> NydusResult: """ CentOS6 implementation of restart_service op that defaults to SysV service :param service_name: Name of the service to be restarted :param is_systemd: Flag indicating if the service to be restarted is systemd unit or not :param should_block: Flag specific to systemd indicating if the we should wait for the completion of the command execution """ return super().restart_service(service_name, is_systemd, should_block) def _enable_qemu_agent(self) -> RunCommandResult: """ Enable the qemu guest agent to start at boot. """ return runCommand(['/sbin/chkconfig', 'qemu-ga', 'on'], 'enable_qemu_agent') class CentOS7(CentOS): OS_VERSION = '7' QEMU_PACKAGE_NAME = 'qemu-guest-agent-2.rpm' def configure_ips(self, *args, **kwargs): super().configure_ip_files(*args, **kwargs) exit_code, outs, errs = runCommand( ['/usr/bin/systemctl', 'restart', 'network'], 'network restart') if exit_code != 0: LOG.error("failed to configure ips: %s (%s)", outs, errs) return False, [outs, errs] return def _yum_configure_repo(self) -> None: """Configures the yum repo. Is a noop for all linux distros except CentOS7, which is EOL""" yum_repo_file = CENTOS6_YUM_REPO # Centos6 and Centos7 has the same file name and path repo_file_contents = read_text( 'customer_local_ops.operating_system.resources', 'centos7_base.repo') create_file(yum_repo_file, repo_file_contents) def configure_mta(self, payload, unused=None, intermediate_result=None): self._yum_configure_repo() return super().configure_mta(payload, unused, intermediate_result) class CentOS8(CentOS): OS_VERSION = '8' QEMU_PACKAGE_NAME = 'qemu-guest-agent-6.rpm' class AlmaLinux8(CentOS8): DISTRO = 'Almalinux' OS_VERSION = '8' QEMU_PACKAGE_NAME = 'qemu-guest-agent-6.rpm' class AlmaLinux9(AlmaLinux8): OS_VERSION = '9' QEMU_PACKAGE_NAME = 'qemu-guest-agent-7.rpm' class Debian(Linux): SYSTEMCTL = '/bin/systemctl' QEMU_PACKAGE_NAME = 'qemu-guest-agent' PANOPTA = 'fm-agent' PANOPTA_CONFIG_FILE = '/etc/fm-agent/fm_agent.cfg' PANOPTA_AGENT_NAME = PANOPTA PANOPTA_STABLE_RELEASE = 'https://repo.fortimonitor.com/deb-stable' PANOPTA_PUBLISHER_URL = 'https://repo.fortimonitor.com/fortimonitor.pub' PANOPTA_MANIFEST_FILE = '/etc/fm-agent-manifest' def __init__(self, package_manager=Apt()): super().__init__(package_manager=package_manager) def remove_file(self, full_file_name): if os.path.exists(full_file_name): LOG.info('Removing: %s', full_file_name) os.unlink(full_file_name) def mount_network_settings(self): data = "" mountpath = "/mnt/cdrom" networkconfig = mountpath + "/openstack/latest/network_data.json" if not os.path.exists(mountpath): os.mkdir(mountpath) command_line = "sudo /bin/mount /dev/sr0 " + mountpath p = subprocess.Popen( # pylint: disable=consider-using-with command_line, shell=True, stdout=subprocess.PIPE) o, e = p.communicate() LOG.info("mount network data drive. stdOut: %s stdErr: %s", o, e) if os.path.isfile(networkconfig): with open(networkconfig, encoding='utf-8') as nc: data = json.load(nc) return data def get_gateway(self, base_cfg): gw = "" for line in base_cfg.split('\n'): if line.strip().startswith('gateway'): gw = line.split()[1] LOG.debug('Gateway: %s', str(gw)) # this is needed due to an update to nocfox ubuntu code. # It should be used as the basis for pulling network configs going forward if str(gw) == "": data = self.mount_network_settings() try: gw = data['networks'][0]['routes'][0]['gateway'] LOG.debug('Updated Gateway: %s', str(gw)) except TypeError: LOG.critical('Could not auto-detect gateway') return str(gw) def get_nameservers(self, base_cfg): ns = "" for line in base_cfg.split('\n'): if line.strip().startswith('dns-nameservers'): ns = line.split()[1:] ns = ' '.join(ns) LOG.debug('nameservers: %s', str(ns)) if str(ns) == "": data = self.mount_network_settings() try: ns = ' '.join([s['address'] for s in data['services'] if s['type'] == 'dns']) LOG.debug('Updated nameservers: %s', str(ns)) except TypeError: LOG.critical('Could not auto-detect nameservers') return ns def configure_ip_files(self, vm_address, addresses, gateway, unused=None): # pylint: disable=R0914 LOG.info("configure_ips: %s, %s, %s", vm_address, addresses, gateway) cfgdir = '/etc/network/interfaces.d' routefile = '/etc/network/if-up.d/update-routes' for oldfile in glob.glob(os.path.join(cfgdir, 'hfs-*.cfg')): self.remove_file(oldfile) # new public config file added on base build self.remove_file(os.path.join(cfgdir, '51-public-ips.cfg')) update_interface_flag = False if os.path.exists(os.path.join(cfgdir, '50-cloud-init.cfg')): with open(os.path.join(cfgdir, '50-cloud-init.cfg'), 'r', encoding='utf-8') as base_cfg_file: base_cfg = base_cfg_file.read() elif os.path.exists('/etc/network/interfaces'): with open('/etc/network/interfaces', 'r', encoding='utf-8') as base_cfg_file: base_cfg = base_cfg_file.read() if str('source /etc/network/interfaces.d/*') not in base_cfg: update_interface_flag = True else: LOG.critical('Default Networking config not found!') base_cfg = '' if update_interface_flag: runCommand(['chattr', '-i', '/etc/network/interfaces'], 'allow write to /etc/network/interfaces', errorOK=True) with open('/etc/network/interfaces', 'a', encoding='utf-8') as base_cfg_file_w: base_cfg_file_w.write('\n'.join(['source /etc/network/interfaces.d/*'])) runCommand(['chattr', '+i', '/etc/network/interfaces'], 'remove write to /etc/network/interfaces', errorOK=True) gw = gateway if not gw: gw = self.get_gateway(base_cfg) LOG.debug('Gateway: %s', str(gw)) ns = self.get_nameservers(base_cfg) self.remove_file(routefile) default_route_created = False for ifnum, ip_address in enumerate(addresses): # we don't need to re-add the default ip as it is not removed if str(ip_address) not in base_cfg: with open(os.path.join(cfgdir, 'hfs-%s.cfg' % str(ip_address)), 'w', encoding='utf-8') as ethcfg: ethcfg.write( '\n'.join( ['auto eth0:%d' % ifnum, 'iface eth0:%d inet static' % ifnum, ' dns-nameservers ' + str(ns), ' address ' + str(ip_address), ' netmask 255.255.255.255', ' up route add -net ' + str(ip_address) + ' netmask 255.255.255.255 dev eth0']) + '\n') LOG.warning('Configured %s to eth0', str(ip_address)) if default_route_created is not True: with open(routefile, 'w', encoding='utf-8') as routecfg: LOG.debug('Writing: %s', routefile) routecfg.write('\n'.join([ '#!/bin/bash', 'ip route del default', 'ip route replace default via ' + str(gw) + ' dev eth0 src ' + str( ip_address) + ' proto static metric 1024', 'exit 0']) + '\n') # chmod 0755 /etc/network/if-up.d/update-routes os.chmod(routefile, 0o755) default_route_created = True UPDATE_ROUTES_PATH = '/etc/network/if-up.d/update-routes' def snapshot_clean(self, payload, unused=None): if not payload['clean']: return # remove public rout file for snapshot. otherwise default route set up by # openstack does not work if os.path.exists(self.UPDATE_ROUTES_PATH): os.unlink(self.UPDATE_ROUTES_PATH) return super().snapshot_clean(payload) def configure_ips(self, *args, **kwargs): self.configure_ip_files(*args, **kwargs) runCommand(['ip', 'addr', 'flush', 'dev', 'eth0'], 'flush old interface data', errorOK=True) exit_code, outs, errs = runCommand( ['/bin/systemctl', 'restart', 'networking'], 'network restart') if exit_code != 0: LOG.error("failed to configure ips: %s (%s)", outs, errs) return False, [outs, errs] return RETRY_INSTALL_PANOPTA_TIMEOUT = 1200 # pylint: disable=invalid-name RETRY_INSTALL_PANOPTA_INTERVAL = 30 # pylint: disable=invalid-name @retry(interval=RETRY_INSTALL_PANOPTA_INTERVAL, timeout=RETRY_INSTALL_PANOPTA_TIMEOUT) def install_panopta(self, payload, *args, **kwargs): """ Create a manifest file for the fm-agent, update the apt-key with fm-agent, update apt-get, and install fm-agent using apt-get :param payload: payload containing customer_key (the key value to set in the manifest file) and template_ids (a csv string of the template ids to assign this server to) :return result of apt-get install operation""" # cleaning panopta-agent keys exit_code, outs, errs = runCommand( ['apt-key', 'del', '61EE28720129F5F3'], 'install {agent}'.format(agent=self.PANOPTA_AGENT_NAME)) sources_file_panopta = '/etc/apt/sources.list' with open(sources_file_panopta, 'r', encoding='utf-8') as source_file: lines = source_file.read() with open(sources_file_panopta, 'w', encoding='utf-8') as source_file: for line in lines.split('\n'): if 'deb http://packages.panopta.com/deb stable main' in line: pass else: source_file.write(line + '\n') sources_file_path = '/etc/apt/sources.list.d' if not os.path.exists(sources_file_path): os.mkdir(sources_file_path) with open(os.path.join(sources_file_path, 'fm_agent.list'), 'w', encoding='utf-8') as source_file: sources_file_addition = '\n'.join(['## Addition to add fm-agent', 'deb [signed-by=/usr/share/keyrings/fortimonitor.pub] ' '{agent_repo} stable main' .format(agent_repo=self.PANOPTA_STABLE_RELEASE)]) source_file.write(sources_file_addition) exit_code, outs, errs = runCommand( "wget -O - {publish_url} | tee /usr/share/keyrings/fortimonitor.pub > /dev/null" .format(publish_url=self.PANOPTA_PUBLISHER_URL), "install {agent}".format(agent=self.PANOPTA_AGENT_NAME), useShell=True) if exit_code != 0: return False, [outs, errs] self._create_panopta_manifest_file(payload) exit_code, outs, errs = runCommand( ['apt-get', 'update', '--fix-missing'], 'install {agent}'.format(agent=self.PANOPTA_AGENT_NAME)) if exit_code != 0: return False, [outs, errs] # debconf is giving some unnecessary errors in the logs, hence setting it as noninteractive cmd = "echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections" runCommand(cmd, 'install {agent}'.format(agent=self.PANOPTA_AGENT_NAME), useShell=True) exit_code, outs, errs = runCommand( ['apt-get', 'install', '-y', self.PANOPTA_AGENT_NAME], 'install {agent}'.format(agent=self.PANOPTA_AGENT_NAME)) silent_install_error = "Installation of fm_agent had an error" in outs return exit_code == 0 and (not silent_install_error), \ self.build_result_dict(outs, errs, 'install_panopta') def delete_panopta(self, *args, **kwargs): """ delete panopta/fortimonitor from the server and removes the panopta/fm_agent manifest file :return result of apt uninstall operation""" try: _, outs, _ = run_command_pipe( 'dpkg --list | grep panopta-agent', 'check panopta installed') _, outs2, _ = run_command_pipe( 'dpkg --list | grep fm-agent', 'check fm-agent installed') if outs: exit_code, outs, errs = runCommand( ['apt-get', 'purge', '-y', 'panopta-agent'], 'delete panopta') if exit_code != 0: LOG.error('failed to remove panopta agent') return False, self.build_result_dict(outs, errs, 'delete panopta') if outs2: exit_code, outs, errs = runCommand( ['apt-get', 'purge', '-y', 'fm-agent'], 'delete fm-agent') if exit_code != 0: LOG.error('failed to remove fm-agent') return False, self.build_result_dict(outs, errs, 'delete fm-agent') else: exit_code, outs, errs = 0, 'panopta/fm agent is not installed', '' if os.path.exists('/etc/panopta-agent-manifest'): os.unlink('/etc/panopta-agent-manifest') exit_code = 0 outs = outs + '\npanopta agent manifest file removed' if os.path.exists('/etc/fm-agent-manifest'): os.unlink('/etc/fm-agent-manifest') exit_code = 0 outs = outs + '\nfm-agent manifest file removed' except (OSError, IOError) as ex: message = "Failed to unlink panopta/fm agent manifest file" LOG.exception(message) return False, self.build_result_dict('', str(ex), 'delete_panopta') return exit_code == 0, self.build_result_dict(outs, errs, 'delete_panopta') def _check_panopta_installed(self): _, outs, _ = run_command_pipe( 'dpkg --list | grep panopta-agent', 'check panopta installed') return outs def update_etc_hosts_hostname(self, hostname: str, ip_addr: str, op_name: str) -> Tuple[int, str, str]: """Updates the /etc/hosts file with the new hostname for the server :param hostname: The new server hostname :param ip_addr: The IP address for the server :param op_name: The op that is running this function """ exit_code, outs, errs = super().update_etc_hosts_hostname(hostname, ip_addr, op_name) if exit_code != 0: return exit_code, outs, errs hostname_prefix = self.get_hostname_prefix(hostname) # ipv4 localhost local_ip = '127.0.1.1' ip_regex = self.get_ip_regex(local_ip) regex = r'^{ip_regex}.*$/{local_ip} {hostname} {hostname_prefix}'.format( ip_regex=ip_regex, local_ip=local_ip, hostname=hostname, hostname_prefix=hostname_prefix, ) return self.regex_replace(HOSTS_PATH, regex, op_name) def _run_change_password_cmd(self, username: str, password: str, op_name: str) -> Tuple[int, str, str]: """ Run the change password command :param username: The Username :param password: The user password :param op_name: The name of the calling op :return: The change password command """ chpasswd_arg = shlex.quote('%s:%s' % (username, password)) cmd = "echo %s | chpasswd" % chpasswd_arg return runCommand(cmd.encode('utf-8'), op_name, useShell=True, omitString=chpasswd_arg.encode('utf-8')) def _install_python_is_python3(self) -> Tuple[int, str, str]: """ Make sure that the 'python' executable runs python3. No install needed by default. :returns: A runCommand result tuple """ return 0, '', '' def _run_add_user_command(self, username) -> Tuple[int, str, str]: """ Run the add user command (differs per linux flavor) :param username: The username :return: exit code, output, errors """ return runCommand(['useradd', '-m', username, '-s', '/bin/bash'], 'add_user') RETRY_INSTALL_QEMU_AGENT_TIMEOUT = 1200 # pylint: disable=invalid-name RETRY_INSTALL_QEMU_AGENT_INTERVAL = 30 # pylint: disable=invalid-name @retry(interval=RETRY_INSTALL_QEMU_AGENT_INTERVAL, timeout=RETRY_INSTALL_QEMU_AGENT_TIMEOUT) def install_qemu_agent(self, pypi_url: str, *args, **kwargs) -> NydusResult: """ Install qemu guest agent on the vm :param pypi_url: The url for the pypi server where the package is located """ result = self._install(self.QEMU_PACKAGE_NAME) if isinstance(result, Retry): return result exit_code, outs, errs = result if exit_code != 0: if 'Nothing to do' not in errs: return False, self.build_result_dict(outs, errs, 'install_qemu_agent') exit_code, outs, errs = self._enable_qemu_agent() if exit_code != 0: return False, self.build_result_dict(outs, errs, 'enable_qemu_agent') return class Debian8(Debian): SYSTEMCTL = '/bin/systemctl' QEMU_PACKAGE_NAME = 'qemu-guest-agent_2.deb' OS_VERSION = '8' DISTRO = 'Debian' def __init__(self, package_manager=AptEOL('Debian8')): super().__init__(package_manager) def install_qemu_agent(self, pypi_url: str, *args, **kwargs) -> NydusResult: """ Install qemu guest agent on the vm :param pypi_url: The url for the pypi server where the package is located """ package_url = pypi_url + '/-/' + self.DISTRO + '/' + self.OS_VERSION + '/' + self.QEMU_PACKAGE_NAME exit_code, outs, errs = runCommand(['wget', package_url], 'Download qemu_guest_agent') if exit_code != 0: return False, self.build_result_dict(outs, errs, 'download_qemu_agent') exit_code, outs, errs = runCommand( ['dpkg', '-i', self.QEMU_PACKAGE_NAME], 'install_qemu_agent') if exit_code != 0: return False, self.build_result_dict(outs, errs, 'install_qemu_agent') exit_code, outs, errs = self._enable_qemu_agent() if exit_code != 0: return False, self.build_result_dict(outs, errs, 'enable_qemu_agent') return class Debian10(Debian): pass class Debian11(Debian10): def _install_python_is_python3(self) -> Tuple[int, str, str]: """ Make sure that the 'python' executable runs python3 :returns: A runCommand result tuple """ return self._install('python-is-python3') class Debian12(Debian11): pass class Ubuntu1604(Debian): SYSTEMCTL = '/bin/systemctl' RETRY_INSTALL_QEMU_AGENT_TIMEOUT = 1200 # pylint: disable=invalid-name RETRY_INSTALL_QEMU_AGENT_INTERVAL = 30 # pylint: disable=invalid-name def __init__(self, package_manager=AptEOL('Ubuntu1604')): super().__init__(package_manager) @retry(interval=RETRY_INSTALL_QEMU_AGENT_INTERVAL, timeout=RETRY_INSTALL_QEMU_AGENT_TIMEOUT) def install_qemu_agent(self, pypi_url: str, *args, **kwargs) -> NydusResult: """ Install qemu guest agent on the vm :param pypi_url: The url for the pypi server where the package is located """ result = self._install(self.QEMU_PACKAGE_NAME) if isinstance(result, Retry): return result exit_code, outs, errs = result if exit_code != 0: if 'Nothing to do' not in errs: return False, self.build_result_dict(outs, errs, 'install_qemu_agent') exit_code, outs, errs = self._enable_qemu_agent() if exit_code != 0: return False, self.build_result_dict(outs, errs, 'enable_qemu_agent') return class Ubuntu2004(Debian): def _install_python_is_python3(self) -> Tuple[int, str, str]: """ Make sure that the 'python' executable runs python3 :returns: A runCommand result tuple """ return self._install('python-is-python3') class Ubuntu2204(Ubuntu2004): pass class Ubuntu2404(Ubuntu2204): pass package_manager.py000064400000012643147205360270010217 0ustar00from typing import Dict, List, Optional import os from shutil import rmtree from pathlib import Path from importlib_resources import read_text from customer_local_ops.util.helpers import create_file from customer_local_ops.util.execute import runCommand, RunCommandResult APT_PUBLIC_KEYS = [ '61EE28720129F5F3' # http://packages.panopta.com/deb ] class PackageManager: # List of command-line arguments to install packages. # Packages to install are appended when command is run. # See install(). INSTALL_ARGS = None # type: List[str] def _env(self) -> Optional[Dict[str, str]]: """Environment variables to send to subprocess. This version copies the environment from the parent process. """ return None def install(self, *packages: str, tag: str = None) -> RunCommandResult: """Install one or more packages. :param *pkg_spec: one or more specifications of packages to install :param tag: optional identifier included in logs :returns: result of installation command as (exit_code, stdout, stderr) """ if not packages: raise ValueError('At least one package is required') cmd = self.INSTALL_ARGS + list(packages) if tag is None: tag = 'install %s' % str(packages) return runCommand(cmd, tag, env=self._env()) def update_indices(self) -> RunCommandResult: """Update local package indices from remote sources. Not all package managers need this, so this version does nothing and returns a fake success result. :returns: result of update command as (exit_code, stdout, stderr) """ return 0, '', '' class Apt(PackageManager): INSTALL_ARGS = ['apt-get', 'install', '-y'] def _env(self) -> Optional[Dict[str, str]]: """Environment variables to send to subprocess. We set DEBIAN_FRONTEND to ensure user is not prompted to answer any questions during installation. """ env = os.environ.copy() env['DEBIAN_FRONTEND'] = 'noninteractive' env['APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE'] = '1' return env def update_indices(self) -> RunCommandResult: for key in APT_PUBLIC_KEYS: cmd = ['apt-key', 'adv', '--keyserver', 'hkp://keyserver.ubuntu.com:80', '--recv-keys', key] runCommand(cmd, 'apt-key get public keys', env=self._env()) cmd = ['apt-get', 'update', '-y', '-o', 'Acquire::ForceIPv4=true'] return runCommand(cmd, 'apt-get update', env=self._env()) class AptEOL(Apt): TMP_DIRS = [ '/tmp/tmp_apt', '/tmp/tmp_apt/source.list.d', '/tmp/tmp_apt/tmp_apt_cache', '/tmp/tmp_apt/tmp_apt_lists' ] TMP_SOURCE_LIST = '/tmp/tmp_apt/source.list' LINUX_EOL_DISTROS = ['Debian8', 'Ubuntu1604'] APT_CONF_PATH = '/etc/apt/apt.conf' RES_PACKAGE = 'customer_local_ops.operating_system.resources' def __init__(self, os_name: str): if os_name not in self.LINUX_EOL_DISTROS: raise ValueError('Invalid Linux distributive name was provided - %s.' % os_name) self.os_name = os_name def _add_tmp_source_list_dirs(self) -> None: """ Add folder structure for temporary APT config \n Fill source.list file with mirrors for provided Linux distributive :returns: None """ for dir_path in self.TMP_DIRS: Path(dir_path).mkdir(parents=True, exist_ok=True) mirrors_list = self.os_name.lower() + '_mirrors.repo' mirrors_list_content = read_text( self.RES_PACKAGE, mirrors_list) create_file(self.TMP_SOURCE_LIST, mirrors_list_content) def _add_tmp_apt_config(self) -> None: """ Fill apt.conf file with a new config :returns: None """ apt_config_content = read_text( self.RES_PACKAGE, 'tmp_apt_config.repo') create_file(self.APT_CONF_PATH, apt_config_content) def _clear_apt_conf(self) -> None: """ Delete apt.conf file :returns: None """ os.remove(self.APT_CONF_PATH) def _clear_tmp_apt_folders(self) -> None: """ Delete all tmp folders used for apt update :returns: None """ rmtree(self.TMP_DIRS[0]) def update_indices(self) -> RunCommandResult: """ Run both _add_tmp_source_list_dirs and _add_tmp_apt_config methods :returns: run update_indices method for super class if no errors; otherwise raise RuntimeError """ try: self._add_tmp_source_list_dirs() self._add_tmp_apt_config() except (PermissionError, IOError, OSError) as ex: raise RuntimeError('An error occurred during file operation with tmp source list: ' + str(ex)) from ex return super().update_indices() def install(self, *packages: str, tag: str = None) -> RunCommandResult: result = super().install(*packages, tag=tag) try: self._clear_apt_conf() self._clear_tmp_apt_folders() except (PermissionError, IOError, OSError) as ex: raise RuntimeError('An error occurred during clear apt.conf file: ' + str(ex)) from ex return result class Yum(PackageManager): INSTALL_ARGS = ['yum', 'install', '-y'] windows.py000064400000071520147205360270006623 0ustar00from pathlib import Path from typing import Any, Dict, List, Union, Tuple import json import logging import os import re from customer_local_ops import NydusResult, Ops, OpType from customer_local_ops.exceptions import DecryptError from customer_local_ops.operating_system import POWERSHELL_PATH from customer_local_ops.util import b64str, execute from customer_local_ops.util.execute import runCommand, run_powershell LOG = logging.getLogger(__name__) def run_powershell_file(script: str, *args: Any, **kw: Any): return execute.run_powershell_file(POWERSHELL_PATH / script, *args, **kw) class Windows(Ops): DISK_UTILIZATION_PATH = r"C:\\" # double backslash still required by Python syntax op_type = OpType.OPERATING_SYSTEM DISTRO = 'Windows' QEMU_PACKAGE_NAME = 'virtio-win.iso' DEVCON = 'devcon.exe' def _get_vm_tag(self): """Return this VM's tag.""" command_get_cn = r""" $s = Get-childitem -Path Cert:\LocalMachine\My\ | Where-Object {$_.Issuer -match "Nydus Customer Services"} | Select-Object -ExpandProperty Subject $s = $s -replace "(CN=)(.*?),.*",'$2' $s """ return run_powershell(command_get_cn, 'get_vm_tag', True) def _run_powershell_op(self, script_file: Union[str, Path], op_name: str, **kw: Any) -> NydusResult: """Run a PowerShell script and return a Nydus result for the outcome. :param script_file: PowerShell script name, no path (must be in operating_system/powershell/) :param op_name: name of the Nydus operation :param kw: additional keyword arguments to pass to run_powershell_file :returns: standard CLO Nydus result with success based on script exit code (see build_result_from_cmd_output) """ tag = kw.pop('tag', op_name) return self.build_result_from_cmd_output( *run_powershell_file(script_file, tag, **kw), op_name) def add_user(self, payload: Dict[str, Any], unused: Any = None) -> NydusResult: """Create a user that can access this server over Remote Desktop. :param payload: dictionary containing username and encrypted_password :returns: result of the operation """ if payload.get('fail_if_exists', False): raise NotImplementedError('fail_if_exists:True not implemented') op_name = 'add_user' username = payload['username'] encrypted_password = payload['encrypted_password'] try: password = self.decrypt(encrypted_password) except DecryptError as ex: return False, self.build_result_dict(ex.outs, ex.errs, op_name) # Having trouble decoding a UTF-8-encoded string with code points above 127 (like 両) on the other side. # Base64-encoding password to limit character set in transit to [a-zA-Z0-9+/=]. password_b64 = b64str(password) exit_code, outs, errs = run_powershell_file( 'add_user.ps1', op_name, script_file_args=[username, 'Remote Desktop Users'], stdin=password_b64, quiet=True) outs = outs.replace(password_b64, '') # Strip stdin echo from stdout return exit_code == 0, self.build_result_dict(outs, errs, op_name) def add_user_to_group(self, username: str, group: str) -> NydusResult: """Add a user to a group. :param username: name of user to add to the group :param group: user will be added to this group :returns: result of the operation """ return self._run_powershell_op( 'add_user_to_group.ps1', 'add_user_to_group', script_file_args=[username, group]) def _user_needs_logout(self, username) -> bool: """Check if the specified user needs to log out :param username: name of user to check :returns: True or False """ get_sessions_command = 'query user' _, outs, _ = run_powershell(get_sessions_command, 'get_active_sessions') if username and (username.lower() in outs): return True return False def remove_user(self, username): op_name = 'remove_user' # check whether user is not performed logout is_user_active = self._user_needs_logout(username) if is_user_active: return False, self.build_result_dict("User is still active.", "User needs to perform logout before user can be removed.", op_name) command = 'net user %s /DELETE ; Remove-Item -Path "C:\\Users\\%s" -Recurse -Force' % (username, username) exit_code, outs, errs = run_powershell(command, op_name) return exit_code == 0, self.build_result_dict(outs, errs, op_name) def change_password(self, payload: Dict[str, Any]) -> NydusResult: """Change a user's password. :param payload: dictionary containing username and new encrypted_password :returns: result of the operation """ op_name = 'change_password' username = payload['username'] encrypted_password = payload['encrypted_password'] try: password = self.decrypt(encrypted_password) except DecryptError as ex: return False, self.build_result_dict(ex.outs, ex.errs, op_name) # Having trouble decoding a UTF-8-encoded string with code points above 127 (like 両) on the other side. # Base64-encoding password to limit character set in transit to [a-zA-Z0-9+/=]. password_b64 = b64str(password) exit_code, outs, errs = run_powershell_file( 'change_password.ps1', op_name, script_file_args=[username], stdin=password_b64, quiet=True) outs = outs.replace(password_b64, '') # Strip stdin echo from stdout return exit_code == 0, self.build_result_dict(outs, errs, op_name) def configure_mta(self, payload, unused=None): # Function is split in order that control panel ops can call underlying os function directly, # without incurring op overhead such as formatted retry results # The `unused` param is an artifact of Archon workflows requiring an I/O # chain for sequencing. # op_name = 'configure_mta' return self.do_configure_mta(payload, op_name) def enable_admin(self, username, unused=None): LOG.info("Adding user sudo permissions for %s via administrators group", username) exit_code, outs, errs = runCommand(['net', 'localgroup', 'administrators', username, '/add'], "add user %s to administrators group" % username) op_name = 'enable_admin' if exit_code != 0: if 'already a member of the group' in errs or 'name is not a member of the group' in errs: LOG.info("Group already properly set.") else: return False, self.build_result_dict(outs, errs, op_name) return self.build_result_dict(outs, errs, op_name) def disable_admin(self, username, unused=None): LOG.info('Removing user sudo permissions for %s ' 'via administrators group', username) op_name = 'disable_admin' commands = [ ('add RDC Access', ['net', 'localgroup', 'Remote Desktop Users', username, '/add']), ('remove Admin Access', ['net', 'localgroup', 'administrators', username, '/delete']), ] for purpose, cmd in commands: exit_code, outs, errs = runCommand(cmd, purpose) if exit_code != 0: if 'already a member of the group' in errs or 'name is not a member of the group' in errs: LOG.info('Group already properly set.') else: return False, self.build_result_dict(outs, errs, op_name) return self.build_result_dict(outs, errs, op_name) def disable_all_admins(self, unused=None): # The `unused` param is an artifact of Archon workflows requiring an I/O # chain for sequencing. # op_name = 'disable_all_admins' command = r""" net user tempHfsAdmin /delete $usersToKeep=@("admin", "Administrator", "cloudbase-init", "DefaultAccount", "Guest", "IME_ADMIN", "IME_USER", "IUSRPLESK_atmail", "IUSRPLESK_horde", "IUSRPLESK_smwebmail", "IUSRPLESK_sqladmin", "IUSR_FS_PUBLIC", "IUSR_FS_UNLISTED", "IWAM_FILESHARING", "IWAM_plesk(default)", "IWAM_sitepreview", "nydus", "Plesk Administrator", "psaadm") Get-CimInstance -ClassName win32_group -Filter "name = 'administrators'" | ` Get-CimAssociatedInstance -Association win32_groupuser | ` %{ if($usersToKeep -notcontains $_.name) { $userName=$_.name "Adding user to 'Remote Desktop Users' group: $userName" net localgroup "Remote Desktop Users" $userName /add "Removing user from 'Administrators' group: $userName" net localgroup "administrators" $userName /delete } }""" exit_code, outs, errs = run_powershell(command, 'Disable All Admins') return exit_code == 0, self.build_result_dict(outs, errs, op_name) def shutdown_clean(self, unused=None): # The `unused` param is an artifact of Archon workflows requiring an I/O # chain for sequencing. # raise NotImplementedError def snapshot_clean(self, payload, unused=None): # pylint: disable=W0221 if not payload['clean']: return op_name = 'snapshot_clean' command = r""" start-transcript -path "C:\Windows\temp\snapClean.txt" $dirs = "C:\\thespian\\director*","C:\\nydus\\log*" Foreach ($dir in $dirs) { $programFolder=try{((Get-ChildItem $dir) | Sort-Object lastwritetime -desc)[0].fullname}catch{"NA"}; "Removing Logs: $($programFolder)\*.log" if(Test-Path $programFolder){ remove-item "$($programFolder)\\*.log" -force -ErrorAction SilentlyContinue; } } $name = 'testaccount' $acct = New-Object Security.Principal.NTAccount($name) $sid = $acct.Translate([Security.Principal.SecurityIdentifier]).Value Get-WmiObject Win32_UserProfile -Filter "sid='${sid}'" | ForEach-Object { $_.Delete() } "Removing Bootscript" $bootScript="C:\\Windows\\Temp\\bootscript.ps1"; if(Test-Path $bootScript){ remove-item $bootScript -force} #clear any non-default users $usersToKeep=@("admin", "Administrator", "cloudbase-init", "DefaultAccount", "Guest", "IME_ADMIN", "IME_USER", "IUSRPLESK_atmail", "IUSRPLESK_horde", "IUSRPLESK_smwebmail", "IUSRPLESK_sqladmin", "IUSR_FS_PUBLIC", "IUSR_FS_UNLISTED", "IWAM_FILESHARING", "IWAM_plesk(default)", "IWAM_sitepreview", "nydus", "Plesk Administrator", "psaadm") Get-WmiObject -query "select Name from Win32_UserAccount where LocalAccount='True'" | ?{$usersToKeep -notcontains $_.name} | %{ $userName=$_.name $cn = [ADSI]"WinNT://$($env:Computername)" "Removing user: $userName" try{ $cn.Delete('User',"$userName") }catch{ $_.Exception.Message } } #clear event logs: function Clear-All-Event-Logs ($ComputerName="localhost"){ $Logs = Get-EventLog -ComputerName $ComputerName -List | ForEach {$_.Log} $Logs | ForEach {Clear-EventLog -Comp $ComputerName -Log $_ } Get-EventLog -ComputerName $ComputerName -List } Clear-All-Event-Logs #remove old cloud-init logs remove-item "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\*.log" #Disable password policy secedit /export /cfg c:\secpol.cfg (gc C:\secpol.cfg).replace("PasswordComplexity = 1", "PasswordComplexity = 0") | Out-File C:\secpol.cfg secedit /configure /db c:\windows\security\local.sdb /cfg c:\secpol.cfg /areas SECURITYPOLICY rm -force c:\secpol.cfg -confirm:$false #add hostname setting $cloudconfpath="C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init.conf" (gc $cloudconfpath) -replace "plugins=cloudbaseinit.plugins.windows.removeqxl.RemoveQXLPlugin",` "plugins=cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin,cloudbaseinit.plugins.windows.removeqxl.RemoveQXLPlugin"` | sc -path $cloudconfpath stop-transcript """ r = run_powershell(command, 'snapShot Cleanup') # return (r[0] == 0,) exit_code, outs, errs = r[0], r[1], r[2] return exit_code == 0, self.build_result_dict(outs, errs, op_name) def snapshot_prep(self, payload): # The goal here is to make the VM only restart gdh-vds if it is on the same IP as it was shut down as. # When a VM is brought up from a published snapshot, we don't want gdh-vds to start # Adding this snapShotIP.txt file alters how serviceRunner.ps1 processes. op_name = 'snapshot_prep' ip = payload['internalAddress'] installLoc = r'C:\Windows\Temp' try: self.write_file(installLoc + r"\snapShotIP.txt", ip) except IOError as ex: LOG.exception("Error writing snapShotIP.sh for %s: %s", ip, ex) return False, self.build_result_dict('', str(ex), op_name) success_message = "snapShotIP.txt written for {0}".format(ip) LOG.info(success_message) return self.build_result_dict(success_message, '', op_name) def do_configure_mta(self, payload, op_name, intermediate_result: Dict[str, Any] = None): relay = payload.get('relay_address') LOG.debug("configuring MTA for %s", relay) if relay is None: return self.build_result_dict( 'No relay; skipping MTA configuration', '', op_name) smtp_registry = r"HKLM\SOFTWARE\Wow6432Node\Mail Enable\Mail Enable\Connectors\SMTP" exit_code, outs, errs = runCommand(['reg', 'add', smtp_registry, '/v', "Forward All Outbound Host", '/d', relay, '/f'], "set mail relay") if exit_code != 0: return False, self.build_result_dict(outs, errs, op_name) exit_code, outs, errs = runCommand(['reg', 'add', smtp_registry, '/v', "Forward All Outbound Enabled", '/d', '1', '/f'], "enable mail relay") if exit_code != 0: # pylint: disable=no-else-return return False, self.build_result_dict(outs, errs, op_name) else: return self.build_result_dict(outs, errs, op_name) def configure_ips(self, vm_address, addresses, gateway, unused=None): op_name = 'configure_ips' exit_code, outs, errs = run_powershell_file( 'setNetwork.ps1', 'configure network', script_file_args=[vm_address] + addresses) if exit_code != 0: LOG.error("failed to configure ips: %s (%s)", outs, errs) return False, self.build_result_dict(outs, errs, op_name) return self.build_result_dict(outs, errs, op_name) def change_hostname(self, payload, unused=None): op_name = 'change_hostname' command = r""" [string]$hostname='""" + payload['hostname'] + """'; if($hostname.tolower().StartsWith('www.')){ $hostname=$hostname.substring(4) } if($hostname.IndexOf('.') -gt -1){ $hostSet=$false; $hostname.Split('.') | %{ if($_.Length -gt 0 -and $hostSet -eq $false){ $hostname=$_; $hostSet=$true; } } } Rename-Computer -NewName $hostname -Force; """ exit_code, outs, errs = run_powershell(command, 'changeHostname') return exit_code == 0, self.build_result_dict(outs, errs, op_name) def _get_cpu_utilization(self): _, outs, _ = run_powershell_file( 'cpu_utilization.ps1', "get_utilization|get_cpu_utilization") utilization = json.loads(outs) cpu = float(utilization['cpuTimePercent']) return { 'cpuUsed': cpu } def _get_memory_utilization(self): _, outs, _ = run_powershell_file( 'memory_utilization.ps1', "get_utilization|get_memory_utilization") utilization = json.loads(outs) memory_total = int(utilization['ramTotalMiB']) memory_free = int(utilization['ramFreeMiB']) memory_used = memory_total - memory_free return { 'memoryTotal': memory_total, 'memoryUsed': memory_used, } def install_panopta(self, payload, *args, **kwargs): # Installs Fortimonitor agent on the server customer_key = payload['customer_key'] template_ids = payload['template_ids'] LOG.info("Installing Fortimonitor agent - Customer Key: %s, Template IDs: %s", customer_key, template_ids) manifest_file_contents = """templates = {template_ids} enable_countermeasures = false""".format(**payload) server_key = payload.get('server_key') if server_key: manifest_file_contents += "\nserver_key = {server_key}".format(server_key=server_key) fqdn = payload.get('fqdn') if fqdn: manifest_file_contents += "\nfqdn = {fqdn}".format(fqdn=fqdn) server_name = payload.get('serverName') if server_name: manifest_file_contents += "\nserver_name = {serverName}".format(serverName=server_name) disable_server_match = payload.get('disable_server_match', False) if disable_server_match: manifest_file_contents += "\ndisable_server_match = true" command = r"""$Manifest = @" {manifest_file_contents} "@ $Manifest | Out-File -FilePath "C:\FortimonitorAgent.manifest" mkdir c:\fortimonitor_temp cd c:\fortimonitor_temp Invoke-WebRequest https://repo.fortimonitor.com/install/win/fortimonitor_agent_windows.ps1 -OutFile c:\fortimonitor_temp\fortimonitor_agent_windows.ps1 # noqa E501 c:\fortimonitor_temp\fortimonitor_agent_windows.ps1 -customer_key {customer_key}"""\ .format(manifest_file_contents=manifest_file_contents, customer_key=customer_key) exit_code, outs, errs = run_powershell(command, 'Install Panopta') return exit_code == 0, self.build_result_dict(outs, errs, 'install_panopta') def _delete_agent(self, agent_name: str) -> Tuple[int, str, str]: """ delete agent if present from the server """ cmd = r"""Get-Package -Name '{agent_name} Agent'""".format(agent_name=agent_name) _, outs, _ = run_powershell(cmd, "Check {agent_name} Installed".format(agent_name=agent_name)) if outs: if agent_name == "Panopta": command = r"""$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -match "Panopta Agent" } $app.Uninstall();""" else: command = r"""$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -match "FortiMonitor Agent" } $app.Uninstall();""" return run_powershell(command, "Delete {agent_name}".format(agent_name=agent_name)) return 0, 'Panopta agent is not installed', '' def _delete_agent_manifest(self, manifest_name: str) -> Tuple[int, str, str]: """ delete agent manifest file """ if os.path.exists(r"C:\{manifest_name}Agent.manifest".format(manifest_name=manifest_name)): command = r"""Remove-Item –path C:\{manifest_name}Agent.manifest –force""".format( manifest_name=manifest_name) return run_powershell(command, "Delete {manifest_name} manifest file".format(manifest_name=manifest_name)) return 0, '{manifest_name} manifest file is not present'.format(manifest_name=manifest_name), '' def _delete_temp_directory(self, temp_dir: str) -> Tuple[int, str, str]: """ delete agent's temporary directory """ if os.path.exists(r"C:\{temp_dir}".format(temp_dir=temp_dir)): command = r"""Remove-Item -path C:\{temp_dir} -Recurse -force""".format( temp_dir=temp_dir) return run_powershell(command, "Delete {temp_dir} directory".format(temp_dir=temp_dir)) return 0, '{temp_dir} directory is not present'.format(temp_dir=temp_dir), '' def delete_panopta(self, *args, **kwargs) -> NydusResult: """ delete panopta and fm-agent if present from the server and removes the panopta/fm-agent manifest file :return result of uninstall operation """ exit_code1, outs, errs = self._delete_agent("Panopta") exit_code2, output, error = self._delete_agent("FortiMonitor") exit_code = exit_code1 + exit_code2 outs = outs + '\n' + output errs = errs + '\n' + error if exit_code != 0: LOG.error('failed to remove panopta/fm-agent agent') return False, self.build_result_dict(outs, errs, 'delete_panopta') try: exit_code1, outs2, errs2 = self._delete_agent_manifest("Panopta") exit_code2, output, error = self._delete_agent_manifest("Fortimonitor") outs2 = outs2 + '\n' + output errs2 = errs2 + '\n' + error outs = outs + '\n' + outs2 errs = errs + '\n' + errs2 exit_code3, outs2, errs2 = self._delete_temp_directory("panopta_temp") exit_code4, output, error = self._delete_temp_directory("fortimonitor_temp") exit_code = exit_code1 + exit_code2 + exit_code3 + exit_code4 outs2 = outs2 + '\n' + output errs2 = errs2 + '\n' + error outs = outs + '\n' + outs2 errs = errs + '\n' + errs2 except (IOError, OSError) as ex: msg = "Failed to unlink Panopta/FortiMonitor manifest file or temporary directory" LOG.exception(msg) return False, self.build_result_dict('', str(ex), 'delete_panopta') return exit_code == 0, self.build_result_dict(outs, errs, 'delete_panopta') def upgrade_panopta(self, *args, **kwargs): """ upgrade panopta on the server :return result of upgrade operation""" cmd = r"""Get-Package -Name 'Panopta Agent'""" exit_code, outs, _ = run_powershell(cmd, "Check Panopta Installed") if outs: command = r"""Invoke-WebRequest https://repo.fortimonitor.com/install/win/fm-upgrade.ps1 -OutFile fm-upgrade.ps1 # noqa E501 pylint: disable=line-too-long .\fm-upgrade.ps1 -autoupdate""" exit_code, outs, errs = run_powershell(command, "Upgrade Panopta") if exit_code != 0: LOG.error('failed to upgrade panopta agent to fortimonitor agent') return False, self.build_result_dict(outs, errs, 'upgrade_panopta') else: exit_code, outs, errs = 0, 'Panopta agent is not installed', '' def get_panopta_server_key(self, *args, **kwargs): """ Look into the Agent.config xml file and find the ServerKey add component. :return: The value of the ServerKey add component in the Agent.config file. """ agent_config = r"""C:\Program Files (x86)\PanoptaAgent\Agent.config""" if not os.path.exists(agent_config): agent_config = r"""C:\Program Files (x86)\FortiMonitorAgent\Agent.config""" cmd = r"""[System.IO.File]::Exists("{agent_config}")""".format(agent_config=agent_config) exit_code, outs, _ = run_powershell(cmd, "Check Config File") if outs == 'False': raise ValueError('Panopta config file not found') command = r"""[xml]$config = Get-Content "{agent_config}" $serverKey = $config.agent.service.add | ? {{$_.key -eq "ServerKey"}} | % {{echo $_.value}} echo $serverKey """.format(agent_config=agent_config) exit_code, outs, _ = run_powershell(command, "Get Server Key") return exit_code == 0, {'outs': outs, 'append_info': False} def update_invalid_resolvers(self, valid_resolvers: List[str], invalid_resolvers: List[str], *args, **kwargs) -> NydusResult: """ If the server has any of the listed invalid resolvers, replace them with the valid resolvers. Otherwise, leave the resolvers as-is. :param valid_resolvers: A list of valid dns nameservers to be added to the server (if needed) :param invalid_resolvers: A list of invalid dns nameservers to be removed from the server :return: A Nydus result """ # Resolver values need to be in a comma-separated list for the powershell script args # e.g. 10.1.1.1,10.1.2.1 valid_resolvers_arg = ','.join(valid_resolvers) invalid_resolvers_arg = ','.join(invalid_resolvers) exit_code, outs, errs = run_powershell_file(script="update_invalid_resolvers.ps1", tag="update_invalid_resolvers", script_file_args=[ '-validResolvers', valid_resolvers_arg, '-invalidResolvers', invalid_resolvers_arg ]) if exit_code != 0: if errs: # Extract plain error message from powershell gubbins. # If format is not as we expect, leave error message as-is. err_array = errs.split('+') err_msg = err_array[0] err_msg_array = err_msg.split(':') if len(err_msg_array) >= 3: errs = err_msg_array[2].strip().replace('\r\n', '') return exit_code == 0, self.build_result_dict(outs, errs, 'update_invalid_resolvers') def enable_winexe(self, *_: Any) -> NydusResult: """Configure the system so it can be accessed remotely with winexe. We use winexe in our integration tests to inspect VMs. :param *_: op chaining arguments :returns: operation result """ return self._run_powershell_op('enable_winexe.ps1', 'enable_winexe') def open_port(self, port: int, *_: Any) -> NydusResult: """Configure the system so the specified port can be accessed remotely. :param port: The port number to open :param *_: op chaining arguments :returns: operation result """ port = int(port) return self._run_powershell_op('configure_port.ps1', 'open_port', script_file_args=[port, "open"]) def close_port(self, port: int, *_: Any) -> NydusResult: """Configure the system so the specified port cannot be accessed remotely. :param port: The port number to close :param *_: op chaining arguments :returns: operation result """ port = int(port) return self._run_powershell_op('configure_port.ps1', 'close_port', script_file_args=[port, "close"]) def install_qemu_agent(self, pypi_url: str, *args): """ Install qemu guest agent on the vm :param pypi_url: The url for the pypi server where the package is located """ package_url = pypi_url + "/-/" + self.DISTRO + "/" + self.QEMU_PACKAGE_NAME return self._run_powershell_op("install_qemu_agent.ps1", "install_qemu_agent", script_file_args=[package_url]) def install_devcon(self, pypi_url: str, *args): """ Install devcon on the vm :param pypi_url: The url for the pypi server where the package is located """ package_url = pypi_url + "/-/" + self.DISTRO + "/" + self.DEVCON return self._run_powershell_op("install_devcon.ps1", "install_devcon", script_file_args=[package_url]) def get_os_info(self, *args: Any) -> Any: """Returns the Operating System information using systeminfo""" op_name = 'get_os_info' os_info = 'NAME=windows\n' command = r""" systeminfo | Select-String "OS" """ exit_code, outs, errs = run_powershell(command, op_name) if exit_code == 0 and outs: for line in outs.split('\n'): if line.startswith('OS Name:'): # Using regular expression to get the version/year. # Example string: "OS Name: Microsoft Windows Server 2022 Standard" pattern = r'\b\d{4}\b' version = re.search(pattern, line) os_info += 'VERSION={version}\n'.format(version=version.group(0)) return os_info return False, self.build_result_dict(outs, errs, op_name) def get_file_info(self, paths: str) -> Dict[str, Any]: """ Op for retrieving file info (if file exists), for now for Windows returning only info whether file exists :param paths: Comma-separated list of paths of files :return: Dict with filenames and files related info (if file present) """ paths_list = map(str.strip, paths.split(",")) def _get_file_dict(path: str) -> Dict[str, Any]: file_loc = Path(os.path.normpath(path)) return { "fileName": path, "exists": file_loc.exists() } out_list = list(map(_get_file_dict, paths_list)) return {"files": out_list} class Windows2016(Windows): pass class Windows2019(Windows): pass class Windows2022(Windows): pass powershell/add_user.ps1000064400000004203147205360270011150 0ustar00param([Parameter(Mandatory)] [String] $userName,[Parameter(Mandatory)] [String] $groupName) $passwordBase64 = Read-Host $password = [System.Text.Encoding]::UTF8.GetString( [System.Convert]::FromBase64String($passwordBase64)) if(Get-WmiObject -query "SELECT * FROM Win32_UserAccount where LocalAccount='True' and name = '$userName'"){ $user = [ADSI]"WinNT://$($env:COMPUTERNAME)/$userName,user" }else{ $cn = [ADSI]"WinNT://$($env:COMPUTERNAME)" $user = $cn.Create("User",$userName) $user.UserFlags = 66049 $user.Put("description", "Local Account") } try{ $user.SetPassword($password) $user.SetInfo() }catch{ $errorMessage=$_.Exception.Message if($errorMessage -like "*Passwords may not contain the user's samAccountName *"){ throw $_.Exception.Message }elseif($errorMessage -like "*The password does not meet the password policy requirements.*"){ $errorMessage secedit /export /cfg c:\secpol.cfg (gc C:\secpol.cfg).replace("PasswordComplexity = 1", "PasswordComplexity = 0") | Out-File C:\secpol.cfg secedit /configure /db c:\windows\security\local.sdb /cfg c:\secpol.cfg /areas SECURITYPOLICY rm -force c:\secpol.cfg -confirm:$false $user.SetPassword($password) $user.SetInfo() secedit /export /cfg c:\secpol.cfg (gc C:\secpol.cfg).replace("PasswordComplexity = 0", "PasswordComplexity = 1") | Out-File C:\secpol.cfg secedit /configure /db c:\windows\security\local.sdb /cfg c:\secpol.cfg /areas SECURITYPOLICY rm -force c:\secpol.cfg -confirm:$false } } $computer = [ADSI]("WinNT://$($env:COMPUTERNAME),computer") $admins = $computer.psbase.children.find('Administrators') $members = $admins.psbase.invoke('Members') | %{$_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null)} if($members -contains $userName){ }else{ $group = [ADSI]"WinNT://$($env:COMPUTERNAME)/$groupName,group" try{ $group.psbase.Invoke("Add",([ADSI]"WinNT://$($env:COMPUTERNAME)/$userName").path) }catch{ $_.Exception.Message } }powershell/add_user_to_group.ps1000064400000001575147205360270013077 0ustar00param([Parameter(Mandatory)] [String] $userName, [Parameter(Mandatory)] [String] $groupName) if(Get-WmiObject -query "SELECT * FROM Win32_UserAccount where LocalAccount='True' and name = '$userName'"){ $user = [ADSI]"WinNT://$($env:COMPUTERNAME)/$userName,user" $computer = [ADSI]("WinNT://$($env:COMPUTERNAME),computer") $admins = $computer.psbase.children.find('Administrators') $members = $admins.psbase.invoke('Members') | %{$_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null)} if($members -contains $userName){ }else{ $group = [ADSI]"WinNT://$($env:COMPUTERNAME)/$groupName,group" try{ $group.psbase.Invoke("Add",([ADSI]"WinNT://$($env:COMPUTERNAME)/$userName").path) }catch{ $_.Exception.Message } } }else{ "User does not exist: $userName" exit 1 } powershell/change_password.ps1000064400000003150147205360270012531 0ustar00param([Parameter(Mandatory)] [String] $userName) $passwordBase64 = Read-Host $password = [System.Text.Encoding]::UTF8.GetString( [System.Convert]::FromBase64String($passwordBase64)) if(Get-WmiObject -query "SELECT * FROM Win32_UserAccount where LocalAccount='True' and name = '$userName'"){ $user = [ADSI]"WinNT://$($env:COMPUTERNAME)/$userName,user" try{     $user.SetPassword($password) $user.SetInfo() }catch{     $errorMessage=$_.Exception.Message     $errorMessage if($errorMessage -like "*Passwords may not contain the user's samAccountName *"){ throw $_.Exception.Message }elseif($errorMessage -like "*The password does not meet the password policy requirements.*"){ $errorMessage         secedit /export /cfg c:\secpol.cfg         (gc C:\secpol.cfg).replace("PasswordComplexity = 1", "PasswordComplexity = 0") | Out-File C:\secpol.cfg         secedit /configure /db c:\windows\security\local.sdb /cfg c:\secpol.cfg /areas SECURITYPOLICY         rm -force c:\secpol.cfg -confirm:$false         $user.SetPassword($password) $user.SetInfo()         secedit /export /cfg c:\secpol.cfg         (gc C:\secpol.cfg).replace("PasswordComplexity = 0", "PasswordComplexity = 1") | Out-File C:\secpol.cfg         secedit /configure /db c:\windows\security\local.sdb /cfg c:\secpol.cfg /areas SECURITYPOLICY         rm -force c:\secpol.cfg -confirm:$false     } } }else{ "User does not exist: $userName" exit 1 } powershell/configure_port.ps1000064400000002737147205360270012421 0ustar00param([Parameter(Mandatory)] [Int] $port, [Parameter(Mandatory)] [String] $action) function Ensure-NetFirewallRule { param($displayName, $direction, $action, $protocol, $localPort) if(Get-NetFirewallRule -DisplayName $displayName -ea SilentlyContinue){ Remove-NetFirewallRule -DisplayName $displayName } New-NetFirewallRule -DisplayName $displayName -Direction $direction -Action $action -Protocol $protocol -LocalPort $localPort } if ($action -eq "open") { "Configuring firewall to open port $port" $ruleName = "Open TCP $port" Ensure-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Action Allow -Protocol TCP -LocalPort $port netsh advfirewall firewall set rule name=$ruleName new localport=$port # Remove any blocks. NOTE: The spaces in the rule names are required. That's how # the rules were created by OH for the images, so need to keep them consistent. Remove-NetFirewallRule -DisplayName "Block TCP $port " -ea SilentlyContinue } elseif ($action -eq "close") { "Configuring firewall to close port $port" # Remove instances of the open port. Remove-NetFirewallRule -DisplayName "Open TCP $port" -ea SilentlyContinue # Block the port $ruleName = "Block TCP $port " Ensure-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Action Block -Protocol TCP -LocalPort $port netsh advfirewall firewall set rule name=$ruleName new localport=$port } else { "Unknown action $action" exit 1 } powershell/cpu_utilization.ps1000064400000000215147205360270012603 0ustar00ConvertTo-Json @{ cpuTimePercent = (Get-WmiObject Win32_PerfFormattedData_PerfOS_Processor -Filter "Name='_Total'").PercentProcessorTime } powershell/enable_winexe.ps1000064400000002220147205360270012164 0ustar00function Ensure-NetFirewallRule { param($displayName, $direction, $action, $protocol, $localPort) if(Get-NetFirewallRule -DisplayName $displayName -ea SilentlyContinue){ Remove-NetFirewallRule -DisplayName $displayName } New-NetFirewallRule -DisplayName $displayName -Direction $direction -Action $action -Protocol $protocol -LocalPort $localPort } "Configuring firewall" Ensure-NetFirewallRule -DisplayName "winexe" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 139 netsh advfirewall firewall set rule name=winexe new localport=139,445 # Remove the blocks set up in the image by OH. NOTE: The spaces in the rule names are required. That's how # the rules were created. Remove-NetFirewallRule -DisplayName "Block TCP 139 " -ea SilentlyContinue Remove-NetFirewallRule -DisplayName "Block TCP 445 " -ea SilentlyContinue "Auto-restart Lanman Server on failure" sc.exe failure LanManServer reset= 3600 actions= restart/5000/restart/10000/restart/5000 "Fix Lanman Server dependencies" # SamSS=Security Accounts Manager; Srv2=SMB2 driver sc.exe config lanmanserver depend= SamSS/Srv2 sc.exe start lanmanserver powershell/install_devcon.ps1000064400000006755147205360270012404 0ustar00param([Parameter(Mandatory)] [String] $package_url) $vertDriver_url = 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.240-1/virtio-win-gt-x64.msi' function Retry-Download { [cmdletbinding()] param([parameter(ValueFromPipeline)][ValidateNotNullorEmpty()][string]$url, [ValidateNotNullorEmpty()][string]$storageDir = "C:\\Windows\\Temp", [ValidateNotNullorEmpty()][int]$maxDownloadAttemptsCount = 20) begin { # We need this change to be able to make requests to urls (e.g. http://hfs-public.secureserver.net), as non-encrypted requests are blocked [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls11 -bor [System.Net.SecurityProtocolType]::Tls12; $webclient = New-Object System.Net.WebClient } Process { $filename = $url.Substring($url.LastIndexOf('/') + 1) if ($filename.indexof('?') -gt -1) { $filename = $filename.split('?')[0] } $file = "$storageDir\\$filename" $count = 0 do { $lastErrMsg = "" start-sleep -seconds ($count * 10) "Downloading: $file Attempt: $($count+1)" try { $webclient.DownloadFile($url, $file) } catch { $lastErrMsg = $_.Exception.Message "$lastErrMsg" } $count++ }while (($count -lt $maxDownloadAttemptsCount) -and (($lastErrMsg -like "*The remote name could not be resolved:*") -or ($lastErrMsg -like "*Unable to connect to the remote server*") -or ($lastErrMsg -like "*The remote server returned an error: (404) Not Found.*"))) if ($lastErrMsg -ne "") { "$lastErrMsg" } } } function InstallDriver { try { Start-Process -Wait -FilePath "C:\Windows\Temp\virtio-win-gt-x64.msi" -ArgumentList "/quiet" } catch { $_.Exception.Message exit 1 } } function IsDevInitialized { $state = 1 try { $state = (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\vioscsi' -Name Start).Start } catch { $state = 1 } return $state } function RemoveDriver { try { Start-Process -FilePath "C:\Windows\Temp\devcon.exe" -ArgumentList "install", '"C:\Program Files\Virtio-Win\Vioscsi\vioscsi.inf"', '"PCI\VEN_1AF4&DEV_1048&SUBSYS_11001AF4&REV_01"' -NoNewWindow $devID = (Get-PnpDevice -Class SCSIAdapter -FriendlyName 'Red Hat VirtIO*' | Select-Object *).HardwareID[0] if ($devID -ne $null) { Start-Process -Wait -FilePath "C:\Windows\Temp\devcon.exe" -ArgumentList "remove $devID" -NoNewWindow $status = IsDevInitialized if ($status -ne 0) { return $false } } else { return $false } } catch { return $false } return $true } Retry-Download $package_url Retry-Download $vertDriver_url InstallDriver $status = IsDevInitialized if ($status -ne 0) { echo "not Initialize" $path = Test-Path 'C:\Program Files\Virtio-Win\Vioscsi\vioscsi.inf' if ($path -is [bool] ) { $dev = RemoveDriver if ($dev -is [bool]) { exit 0 } else { exit 1 } } } else { exit 0 }powershell/install_panopta.ps1000064400000000274147205360270012556 0ustar00Invoke-WebRequest https://packages.panopta.com/install/panopta_agent_windows.ps1 -OutFile panopta_agent_windows.ps1 .\panopta_agent_windows.ps1 -customer_key $customer_key -server_key NONEpowershell/install_qemu_agent.ps1000064400000004355147205360270013245 0ustar00param([Parameter(Mandatory)] [String] $package_url) function Retry-Download{ [cmdletbinding()] param([parameter(ValueFromPipeline)][ValidateNotNullorEmpty()][string]$url, [ValidateNotNullorEmpty()][string]$storageDir="C:\\Windows\\Temp", [ValidateNotNullorEmpty()][int]$maxDownloadAttemptsCount=20) begin{ # We need this change to be able to make requests to urls (e.g. http://hfs-public.secureserver.net), as non-encrypted requests are blocked [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls11 -bor [System.Net.SecurityProtocolType]::Tls12; $webclient = New-Object System.Net.WebClient } Process { $filename=$url.Substring($url.LastIndexOf('/')+1) if($filename.indexof('?') -gt -1){ $filename=$filename.split('?')[0] } $file = "$storageDir\\$filename" $count=0 do{ $lastErrMsg="" start-sleep -seconds ($count*10) "Downloading: $file Attempt: $($count+1)" try{ $webclient.DownloadFile($url,$file) }catch{ $lastErrMsg=$_.Exception.Message "$lastErrMsg" } $count++ }while(($count -lt $maxDownloadAttemptsCount) -and (($lastErrMsg -like "*The remote name could not be resolved:*") -or ($lastErrMsg -like "*Unable to connect to the remote server*") -or ($lastErrMsg -like "*The remote server returned an error: (404) Not Found.*"))) if($lastErrMsg -ne ""){ "$lastErrMsg" } } } # Remove mount to ISO - in case previous attempt failed Dismount-DiskImage -ImagePath "C:\\Windows\\Temp\\virtio-win.iso" Retry-Download $package_url Mount-DiskImage -ImagePath "C:\\Windows\\Temp\\virtio-win.iso" $driveLetter=gwmi Win32_LogicalDisk | Where {$_.DriveType -eq 5 -and $_.VolumeName -like 'virtio-win*'} | select -ExpandProperty DeviceId # Install qemu agent Start-Process msiexec.exe -Wait -ArgumentList "/I ${driveLetter}\guest-agent\qemu-ga-x86_64.msi /quiet /norestart" -NoNewWindow # Remove mount to ISO Dismount-DiskImage -ImagePath "C:\\Windows\\Temp\\virtio-win.iso" # Enable qemu to start on boot Set-Service QEMU-GA -StartupType Automatic powershell/memory_utilization.ps1000064400000000264147205360270013330 0ustar00ConvertTo-Json @{ ramFreeMiB = (Get-WmiObject Win32_OperatingSystem).FreePhysicalMemory/1024 ramTotalMiB = (Get-WmiObject Win32_ComputerSystem).TotalPhysicalMemory/1024/1024 } powershell/setNetwork.ps1000064400000005723147205360270011537 0ustar00Start-Transcript -Path C:\Windows\Temp\setNetwork.log -Append $endPointAddr=$Args $taskName="HostingNetworkInitialization" $taskScript="C:\Windows\Tasks\initializeHostingNetwork.ps1" $currentIps=Get-NetAdapter -Name Ethernet | Get-NetIPAddress -IPAddress "*" # Delete HostingNetworkInitialization script, then stop the task if(Test-Path $taskScript){Remove-Item $taskScript -Force} schtasks.exe /End /TN $taskName schtasks.exe /Query /TN $taskName /FO LIST /V #if only one ip configured if($endPointAddr.gettype() -eq 'String'){ $endPointAddr=@($endPointAddr) } #make sure we have the saved IPs in the IPs to set 'privateip.txt','publicip.txt' | %{ if(Test-Path "C:\Windows\Tasks\$($_)"){ $initialIp=Get-Content "C:\Windows\Tasks\$($_)" if($endPointAddr -notcontains $initialIp){ $endPointAddr+=@($initialIp) } } } "Start-Transcript -Path C:\Windows\Temp\initializeHostingNetwork.log" | Out-File $taskScript -encoding ASCII -append #compare and modify accordingly $ipSetCompare=Compare-Object $endPointAddr $($currentIps.IpAddress) -IncludeEqual $ipSetCompare | %{ $ipaddress=$($_.InputObject) if($_.SideIndicator -eq '<='){ "Adding $ipaddress" Get-NetAdapter -Name Ethernet | New-NetIPAddress -AddressFamily IPv4 -IPAddress $ipaddress -PrefixLength 32 -SkipAsSource $false "Set-NetIPAddress -IPAddress $ipaddress -SkipAsSource `$False" | Out-File $taskScript -encoding ASCII -append }elseif($_.SideIndicator -eq '=>'){ "Removing $ipaddress" Get-NetIPAddress -IPAddress $_.InputObject | Remove-NetIPAddress -Confirm:$false }else{ #IP not affected $ipObject = Get-NetAdapter -Name Ethernet | Get-NetIPAddress | ?{$_.ipaddress -eq $ipaddress} if($ipObject.PrefixLength -eq 32){ #if there is a public or addon IP, set this to outbound traffic "Set-NetIPAddress -IPAddress $ipaddress -SkipAsSource `$False" | Out-File $taskScript -encoding ASCII -append }else{ #count only ips that will remain if(($ipSetCompare | ?{$_.SideIndicator -ne '=>'}).count -lt 2){ #if there is only the private ip, set this to outbound traffic "Set-NetIPAddress -IPAddress $ipaddress -SkipAsSource `$False" | Out-File $taskScript -encoding ASCII -append }else{ #otherwise we do not want outbound traffic on the private ip "Set-NetIPAddress -IPAddress $ipaddress -SkipAsSource `$True" | Out-File $taskScript -encoding ASCII -append } } } } "Get-NetIPAddress -InterfaceAlias Ethernet" |Out-File $taskScript -encoding ASCII -append "Stop-Transcript" |Out-File $taskScript -encoding ASCII -append # Run the task script we just wrote powershell.exe -ExecutionPolicy Unrestricted -File $taskScript # Log end state for posterity "Final state:" Get-NetIPAddress -InterfaceAlias Ethernet Stop-Transcript powershell/update_invalid_resolvers.ps1000064400000012372147205360270014464 0ustar00param([Parameter(Mandatory)] [String] $validResolvers, [Parameter(Mandatory)] [String] $invalidResolvers) function Get-Nic { if ($adapter = Get-NetAdapter -Name Ethernet) { return Get-WmiObject -Class "Win32_NetworkAdapterConfiguration" -Namespace "root\CIMV2" | Where-Object {$_.InterfaceIndex -eq $adapter.ifIndex} } else { throw "Adapter with name 'Ethernet' not found" } } function Test-Resolvers { # Use an internally and publicly resolvable domain to test, as the vm may not have a public IP $domain = "hfs-public.godaddy.com" return Test-NetConnection -Computername $domain -InformationLevel Quiet } function Backup-Resolvers { param([Parameter(Mandatory)] [String[]] $currentResolvers) $filePath = "C:\Windows\Temp\resolvers" "Backing up resolvers to file $filePath" $current_resolvers | Out-File -FilePath $filePath } function Restore-Resolvers { $filePath = "C:\Windows\Temp\resolvers" "Restoring resolvers from backup file $filePath" $old_resolvers = Get-Content $filePath "Old resolvers: $old_resolvers" $nic = Get-Nic $nic.SetDNSServerSearchOrder($old_resolvers) } function Remove-Backup { $filePath = "C:\Windows\Temp\resolvers" "Removing backup file $filePath" Remove-Item -Path $filePath -Force -ErrorAction SilentlyContinue } try { "Valid resolvers: $validResolvers" $invalid_resolvers_present = $False "Invalid resolvers: $invalidResolvers" $validResolversArray = $validResolvers.Split(",") $invalidResolversArray = $invalidResolvers.Split(",") $nic = Get-Nic $current_resolvers = $nic.DNSServerSearchOrder "Current resolvers are: $current_resolvers" $new_resolvers = @() # Loop through current resolvers and check for invalid resolvers. If a resolver is not invalid, # add it to the new resolvers list. foreach ($current_resolver in $current_resolvers) { if ($invalidResolversArray.Contains($current_resolver)) { "Current resolvers have invalid resolver: $current_resolver" $invalid_resolvers_present = $True } else { # Add resolver to new resolvers list $new_resolvers = $new_resolvers + $current_resolver } } "Invalid resolvers present: $invalid_resolvers_present" if ($invalid_resolvers_present) { # Test if network resolves. If it doesn't, we can't test if new resolvers will work. if (Test-Resolvers) { # We can proceed to trying to update the resolvers "Existing resolvers can resolve test domain" # Backup the existing resolvers before making any changes Backup-Resolvers -currentResolvers $current_resolvers # Update the resolvers # Add the valid resolvers to the new_resolvers list, if they are not there already foreach ($valid_resolver in $validResolversArray) { if (!$new_resolvers.Contains($valid_resolver)) { $new_resolvers = $new_resolvers + $valid_resolver } } $nic = Get-Nic "Setting new resolvers: $new_resolvers" $ret = $nic.SetDNSServerSearchOrder($new_resolvers) $exit_code = $ret.ReturnValue "ReturnValue is $exit_code" if ($exit_code -eq 0) { # Update was OK "Resolvers updated successfully" } elseif ($exit_code -eq 1) { # Update was OK, but a reboot is required! # As we can't perform a reboot here, we'll test the new resolvers and revert to backup if they are not working "Resolvers were updated but a reboot may be required" } else { # Something went wrong with the update throw "Unable to update resolvers" } # Test if network resolves if (Test-Resolvers) { "New resolvers can resolve test domain successfully" $nic = Get-Nic $updated_resolvers = $nic.DNSServerSearchOrder "New resolvers: $updated_resolvers" Remove-Backup exit 0 } else { # Restore the backup "Invalid resolvers were replaced, but test domain is not resolving." Restore-Resolvers # Re-test resolvers if (Test-Resolvers) { "Restored resolvers can resolve test domain" Remove-Backup throw "Resolvers were replaced but old resolvers had to be restored, as new resolvers could not resolve test domain." } else { throw "Restored resolvers cannot resolve test domain. Something may have gone wrong with the restore." } } } else { # We have no way to test if new resolvers will work, as the current configuration is not resolving throw "Current resolvers cannot resolve test domain. Unable to proceed." } } else { # There are no invalid resolvers in the current resolver list. Nothing to do here. "There are no invalid resolvers to replace." exit 0 } } catch { "An error occurred: $_" Write-Error $Error[0].ToString() exit 1 } resources/__init__.py000064400000000000147205360270010663 0ustar00resources/centos6_base.repo000064400000004337147205360270012035 0ustar00# CentOS-Base.repo # # The mirror system uses the connecting IP address of the client and the # update status of each mirror to pick mirrors that are updated to and # geographically close to the client. You should use this for CentOS updates # unless you are manually picking other mirrors. # # If the mirrorlist= does not work for you, as a fall back you can try the # remarked out baseurl= line instead. # # [base] name=CentOS-$releasever - Base #mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os&infra=$infra #baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/ baseurl=http://vault.centos.org/6.10/os/$basearch/ gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6 #released updates [updates] name=CentOS-$releasever - Updates #mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates&infra=$infra #baseurl=http://mirror.centos.org/centos/$releasever/updates/$basearch/ baseurl=http://vault.centos.org/6.10/updates/$basearch/ gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6 #additional packages that may be useful [extras] name=CentOS-$releasever - Extras #mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras&infra=$infra #baseurl=http://mirror.centos.org/centos/$releasever/extras/$basearch/ baseurl=http://vault.centos.org/6.10/extras/$basearch/ gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6 #additional packages that extend functionality of existing packages [centosplus] name=CentOS-$releasever - Plus #mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=centosplus&infra=$infra #baseurl=http://mirror.centos.org/centos/$releasever/centosplus/$basearch/ baseurl=http://vault.centos.org/6.10/centosplus/$basearch/ gpgcheck=1 enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6 #contrib - packages by Centos Users [contrib] name=CentOS-$releasever - Contrib #mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=contrib&infra=$infra #baseurl=http://mirror.centos.org/centos/$releasever/contrib/$basearch/ baseurl=http://vault.centos.org/6.10/contrib/$basearch/ gpgcheck=1 enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6 resources/centos7_base.repo000064400000003205147205360270012027 0ustar00# CentOS-Base.repo # # The mirror system uses the connecting IP address of the client and the # update status of each mirror to pick mirrors that are updated to and # geographically close to the client. You should use this for CentOS updates # unless you are manually picking other mirrors. # # If the mirrorlist= does not work for you, as a fall back you can try the # remarked out baseurl= line instead. # # [base] name=CentOS-$releasever - Base #mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os&infra=$infra baseurl=http://vault.centos.org/centos/$releasever/os/$basearch/ gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 enabled = 1 #released updates [updates] name=CentOS-$releasever - Updates #mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates&infra=$infra baseurl=http://vault.centos.org/centos/$releasever/updates/$basearch/ gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 #additional packages that may be useful [extras] name=CentOS-$releasever - Extras #mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras&infra=$infra baseurl=http://vault.centos.org/centos/$releasever/extras/$basearch/ gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 #additional packages that extend functionality of existing packages [centosplus] name=CentOS-$releasever - Plus #mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=centosplus&infra=$infra baseurl=http://vault.centos.org/centos/$releasever/centosplus/$basearch/ gpgcheck=1 enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 resources/debian8_mirrors.repo000064400000000337147205360270012545 0ustar00deb [trusted=yes] http://archive.debian.org/debian jessie main deb [trusted=yes] http://archive.debian.org/debian-security jessie/updates main deb [trusted=yes] http://archive.debian.org/debian/ jessie main contrib non-freeresources/fortimonitor.repo000064400000000255147205360270012210 0ustar00[fortimonitor.repo] name=FortiMonitor Repository baseurl=https://repo.fortimonitor.com/rpm-stable/ enabled=1 gpgcheck=1 gpgkey=https://repo.fortimonitor.com/fortimonitor.pubresources/hfs_common.repo000064400000000236147205360270011604 0ustar00[hfs-common] name=Hosting Foundation Services common repository baseurl=https://yum.secureserver.net/repos/dev/centos/7/x86_64/hfs-common enabled=1 gpgcheck=0resources/panopta.repo000064400000000232147205360270011112 0ustar00[panopta.repo] name=Panopta Repository baseurl=http://packages.panopta.com/rpm-stable/ enabled=1 gpgcheck=1 gpgkey=http://packages.panopta.com/panopta.pubresources/panopta_centos8.repo000064400000000233147205360270012556 0ustar00[panopta.repo] name=Panopta Repository baseurl=http://packages.panopta.com/rpm8-stable/ enabled=1 gpgcheck=1 gpgkey=http://packages.panopta.com/panopta.pubresources/tmp_apt_config.repo000064400000000275147205360270012450 0ustar00Dir::Etc::SourceParts "/tmp/tmp_apt/source.list.d"; Dir::Etc::SourceList "/tmp/tmp_apt/source.list"; Dir::State::Lists "/tmp/tmp_apt/tmp_apt_lists"; Dir::Cache "/tmp/tmp_apt/tmp_apt_cache";resources/ubuntu1604_mirrors.repo000064400000000536147205360270013071 0ustar00deb http://archive.ubuntu.com/ubuntu/ xenial main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu/ xenial-backports main restricted universe multiverse deb http://security.ubuntu.com/ubuntu xenial-security main restricted universe multiverseresources/update_dns_linux.sh000064400000004363147205360270012473 0ustar00#!/usr/bin/env bash invalid_resolvers1=$1 invalid_resolvers2=$2 valid_resolvers1=$3 valid_resolvers2=$4 if [[ -z "$invalid_resolvers1" ]] || [[ -z "$invalid_resolvers2" ]] || [[ -z "$valid_resolvers1" ]] || [[ -z "$valid_resolvers2" ]] then echo "usage: ./update_dns_centos.sh INVALID_RESOLVERS_1 INVALID_RESOLVERS_2 VALID_RESOLVERS_1 VALID_RESOLVERS_2" exit 2 fi check_net_mgr="$(ps -e | grep -i networkmanager)" if [[ ! -z "$check_net_mgr" ]] then echo "Network Manager is running on the box, skipping changes" >/dev/stderr exit 1 fi check_dns1="$(grep "$invalid_resolvers1" /etc/resolv.conf)" check_dns2="$(grep "$invalid_resolvers2" /etc/resolv.conf)" err_msg="no servers could be reached" if [[ ! -z "$check_dns1" ]] || [[ ! -z "$check_dns2" ]] then echo "resolv.conf has invalid resolvers" chk_domain_resolv="$(dig google.com +short)" if [[ ! -z "$chk_domain_resolv" ]] && [[ "$chk_domain_resolv" != *"$err_msg"* ]] then echo "Domain getting resolved successfully with invalid resolvers" cp /etc/resolv.conf /var/tmp echo "resolv.conf backup created at /var/tmp" echo "Updating invalid resolvers with valid ones" sed -i "s/$invalid_resolvers1/$valid_resolvers1/g" /etc/resolv.conf success_check1=$? sed -i "s/$invalid_resolvers2/$valid_resolvers2/g" /etc/resolv.conf success_check2=$? if [[ "$success_check1" -ne 0 ]] || [[ "$success_check2" -ne 0 ]] then echo "DNS resolvers could not be replaced, restoring backup" >/dev/stderr cp /var/tmp/resolv.conf /etc/resolv.conf exit_code=2 else check_domain="$(dig google.com +short)" if [[ -z "$check_domain" ]] || [[ "$check_domain" == *"$err_msg"* ]] then echo "Valid resolvers could not resolve domain, restoring backup" >/dev/stderr cp /var/tmp/resolv.conf /etc/resolv.conf exit_code=2 else echo "Valid resolvers resolved the domain successfully" exit_code=0 fi fi echo "Deleting backup" rm /var/tmp/resolv.conf exit "$exit_code" else echo "Invalid resolvers did not resolve domain, skipping changes" >/dev/stderr exit 2 fi else echo "resolv.conf does not have invalid resolvers, skipping changes" exit 0 fi resources/__pycache__/__init__.cpython-38.pyc000064400000000302147205360270015156 0ustar00U af@sdS)Nrrrg/opt/nydus/tmp/pip-target-53d1vnqk/lib/python/customer_local_ops/operating_system/resources/__init__.py__pycache__/__init__.cpython-38.pyc000064400000000547147205360270013157 0ustar00U af@s.ddlmZeejZedZedZdS))PathZ powershell resourcesN)pathlibr__file__parentresolveZSCRIPT_BASEPATHZPOWERSHELL_PATHZSHELL_SCRIPT_PATHrr]/opt/nydus/tmp/pip-target-53d1vnqk/lib/python/customer_local_ops/operating_system/__init__.pys __pycache__/linux.cpython-38.pyc000064400000212672147205360270012563 0ustar00U afnt@slddlZddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl Z ddl Z ddl m Z ddl mZddlmZddlmZmZmZmZmZmZddlmZddl mZddlmZdd lmZdd lm Z m!Z!m"Z"dd l#m$Z$dd l%m&Z&dd l'm(Z(m)Z)m*Z*m+Z+ddl,m-Z-m.Z.m/Z/m0Z0ddl1m2Z2m3Z3m4Z4m5Z5ddl6m7Z7m8Z8m9Z9e:e;Zej=j?ddddZ@ej=>ej=j?ddZAdZBdZCej=>ej=j?ddZDej=>ej=j?dddZEej=>ej=j?ddddZFdZGdZHd ZId!ZJeKd"ZLeKd#ZMd$ZNd%gZOee8e.fZPGd&d'd'eQeZRGd(d)d)eSZTd*d+ZUeQeQd,d-d.ZVd/d0ZWd1d2ZXd3d4ZYGd5d6d6e ZZGd7d8d8eZZ[Gd9d:d:e[Z\Gd;d<dd>e[Z^Gd?d@d@e^Z_GdAdBdBe_Z`GdCdDdDeZZaGdEdFdFeaZbGdGdHdHeaZcGdIdJdJecZdGdKdLdLedZeGdMdNdNeaZfGdOdPdPeaZgGdQdRdRegZhGdSdTdTehZidS)UN)datetime)Enum)Path)OptionalTupleUnionListDictAny) OrderedDict)rmtree)load_pem_x509_certificate) read_text)OpsOpType NydusResult) DecryptError)SHELL_SCRIPT_PATH)AptAptEOLPackageManagerYum) runCommandRunCommandResultrun_command_piperun_shell_script_file) append_lineedit_file_lines replace_line create_file)retryRetryRETRYoptnydussslz executor.crtetcz sudoers.d)r$z 48-wp-toolkit)ZicingahostsZcloudz cloud.cfgz cloud.cfg.dz50_preserve_hostname.cfgZupdate_etc_hostsz!/etc/yum.repos.d/CentOS-Base.repoz /etc/yum.repos.d/hfs-common.repoiz\^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\-(?P0|[1-9]\d*)$z^(?P0|[1-9]\d*)$z/etc/os-releaseZXdjw6fGvTto3MktGc@seZdZdZdZdZdS)NydusUpdateTypeupgrade downgrade reinstallN)__name__ __module__ __qualname__UPGRADE DOWNGRADE REINSTALLr2r2Z/opt/nydus/tmp/pip-target-53d1vnqk/lib/python/customer_local_ops/operating_system/linux.pyr(<sr(cseZdZfddZZS)UnsupportedUnitcstd||fdS)Nz3Unsupported unit in fields %s. Supported units: %ssuper__init__)selffieldsZsupported_units __class__r2r3r7Cs zUnsupportedUnit.__init__)r,r-r.r7 __classcell__r2r2r:r3r4Bsr4c Csbtjt|}zt|d|t|dWn0ttfk r\d||f}t |YnX|S)Nz%s ALL=(ALL) NOPASSWD: ALL i z(failed to write sudoers file for %s (%s)) ospathjoinSUDOERS_PREFIXrchmodOSErrorIOErrorLOG exceptionusernamer>messager2r2r3 add_sudoerIs  rIrGreturnc Csr|tkrtd|tjt|}ztj|r:t|Wn0tt fk rld||f}t |YnX|S)z Remove sudoer permissions from a user. :param username: The username :raises: OSError, IOError if the path can't be deleted :return: The path to the sudoer file z'Cannot remove sudo for system user "%s"z)failed to remove sudoers file for %s (%s)) SYSTEM_USERS ValueErrorr=r>r?r@existsunlinkrBrCrDrErFr2r2r3 remove_sudoerUs    rPcCs*ddttD}|D] }t|qdS)NcSs"g|]}|tkr|ts|qSr2)rL startswithEXEMPT_SUDOERS_PREFIX.0fr2r2r3 ks z"remove_sudoers..)r=listdirr@rP)filelistrUr2r2r3remove_sudoersjsrYc CsVztdtdWn8ttfk rP}ztdt|W5d}~XYnXdS)Nz shutdown VMzshutdown -h nowz"failed to shutdown vm. error: '%s')rDinfor=systemrBrCrEstr)exr2r2r3shutdownqs r^cCs>ttj}d}||d}|dk r.|||<n ||d|S)NZLD_LIBRARY_PATHZ_ORIG)dictr=environgetpop)envZlp_keyZlp_origr2r2r3 set_safe_env{s   rdc sPeZdZdZedgZdddddgZdZd Zd Z d Z d Z d Z dZ ejZdZdeeddfdd ZddZeedddZdddZeeeeefdddZdedddZd d!Zeeeeeeefd"d#d$Z dd%d&Z!dd'd(Z"dd)d*Z#ee$d+d,d-Z%deeeeeeefd.d/d0Z&d1d2Z'dd3d4Z(dd5d6Z)d7Z*d8Z+e,e+e*d9dd:d;Z-de.ee/fd<d=d>Z0dedd?d@dAZ1dBdCZ2ddDdEZ3eedFdGdHZ4eedIdJdKZ5deeeeeeefdLdMdNZ6eeeeeeefdOdPdQZ7eeeefddRdSZ8eeeefddTdUZ9e/e/dVdWdXZ:ddYdZZ;d[d\ZdadbZ?dddcddZ@dedfe$eeeefdgdhdiZAdjdkZBddleedldmdnZCdodpZDdqdrZEdsdtZFdudvZGdwdxZHeIeeIeedydzd{ZJeKdd|d}d~ZLeeeeeee$e$ed ddZMeeKeedddZNddZOeeKeedddZPeeKeeedddZQdeeeeeee$e$ed ddZRdeee$edddZSdee$e$edddZTeeedddZUee.ee/fdddZVee.ee/fdddZWeIedddZXd7ZYd8ZZe,eZeYd9eedddZ[e\dddZ]e\dddZ^Z_S)Linux/ZkBzCould not resolve hostzCould not get lockzhas no installation candidatezcannot sync correctlyzError performing handshake/etc/fm-agent-manifest/etc/fm-agent/fm_agent.cfgfm-agentz"/etc/yum.repos.d/fortimonitor.repozfortimonitor.repo/usr/bin/systemctlz/etc/sysconfig/qemu-gaN)package_managerrKcst||_dSN)r6r7rkr8rkr:r2r3r7s zLinux.__init__c Csd}ttd|}|}t|}|j}|d}|D]$}|dr8|dd}q^q8|dk r|d|dfW5QRSW5QRd SQRXdS) zReturn this VM's tag.Nrb,zCN==r)rqrrzUnable to retrieve vm tag) openNYDUS_EXECUTOR_CRT_PATHreadr subjectZrfc4514_stringsplitrQstrip)r8tag cert_fileZ cert_contentscertrvZ subject_listitemr2r2r3 _get_vm_tags    zLinux._get_vm_tagpackagesrKc ss tdt}|dd}|s.jj|d<fdd|d<|D]T\}}|\}}tfdd jDrt d t S|d krH|dkrHqqH||fS) aqInstall packages with operating system's package manager. :param *packages: specifications of one or more packages to install :returns: - a Retry object if a retryable error occurred, or - result of last command ran; execution stops on first non-zero exit code :raises ValueError: when no packages are specified z At least one package is requiredskip_update_indicesFupdate_indicescs jjSrl)rkinstallr2)rr8r2r3z Linux._install..rc3s|]}|kVqdSrlr2)rTe)errsr2r3 sz!Linux._install..zTemporary error, will retry: %sr) rMr rarkritemsanyRETRYABLE_INSTALL_ERRORSrDwarningr") r8rkwargscommandsrmethodcommand exit_codeoutsr2)rrr8r3_installs     zLinux._installc Csd}|d}|dd}tdd|gddd\}}}|d kr^|rtd |d ||||fSn8||\}}}|d krtd ||d ||||fS|||S) Nadd_userrGfail_if_existsTZgetentpasswdZdoes_user_existerrorOKrzUser %s already existsFzfailed to add user: %s (%s))rarrDerrorbuild_result_dict_run_add_user_command_change_password) r8payloadunusedop_namerGrrrrr2r2r3rs"   zLinux.add_userrJcCstdd|gdS) Run the add user command (differs per linux flavor) :param username: The username :return: exit code, output, errors useradd-mrrr8rGr2r2r3rszLinux._run_add_user_commandrKc Csd}td|dddtd|ddd\}}}|dkrVtd||d||||fSz t|WnRttfk r}z0td d t|d|d t||fWYSd }~XYnX||||S) z Remove the user from the server :param username: The username :param unused: Parameter used for workflow chaining :return: Nydus Result Dict remove_userz pkill -u %sTuseShellzuserdel -fr %srzfailed to remove user: %s (%s)Fz failed to remove sudoer: %s (%s)rrN)rrDrrrPrBrCr\)r8rGrrrrrr]r2r2r3rs$  ,zLinux.remove_userc Cs|d}|d}z||}Wn<tk rZ}zd||j|j|fWYSd}~XYnX||||\}}} |dkrtd|| d||| |fS||| |S)NrGencrypted_passwordFrz"failed to change password: %s (%s))decryptrrrr_run_change_password_cmdrDr) r8rrrGrpasswordr]rrrr2r2r3rs,zLinux._change_passwordrGrrrKcCs*td||f}d|}t||d|dS) Run the change password command :param username: The Username :param password: The user password :param op_name: The name of the calling op :return: The change password command %s:%secho %s | chpasswdTrZ omitString)shlexquoterr8rGrrZ chpasswd_argcmdr2r2r3rs zLinux._run_change_password_cmdcCs ||dS)Nchange_password)rr8rrr2r2r3rszLinux.change_passwordc Csd}}d}z t|}WnXttfk rp}z6td||t|t|}d||||fWYSd}~XYnXtd||||||S)Nrr enable_adminz*%s: failed to enable admin for user %s: %sFz%s: Enabled admin for user %s)rIrBrCrDrEr\rrZ)r8rGrrrrr]r2r2r3rs (zLinux.enable_admincCszd}tdttd}z6|||\}}}||||}|dkrNd|fWSWn$tk rttd||YnX|S)Ndisable_all_adminsz*Removing user sudo permissions for * at %sZ temphfsadminrFz%s: error removing '%s' account)rDrZr@rYremove_accountr ExceptionrE)r8rrZadmin_account_namerstdoutstderrZ result_dictr2r2r3r,s zLinux.disable_all_admins) account_namerKcCs*zt|Wntk r$YdSXdS)z Check to see if the specified account exists. :param account_name: The name of the account for which to check :returns: True if the account exists; otherwise, false FT)pwdgetpwnamKeyError)r8rr2r2r3account_exists=s zLinux.account_exists)rrrKcCs.|dkr d}||sdStddd|g|dS)a Remove the specified account, if it exists :param account_name: The name of the account to remove :param op_name: Optional string indicating the name of the parent op. If not specified, it will default to `"remove_account"` :returns: A 3-tuple containing an integer indicating the success or failure of the overall operation and two strings containing stdout and stderr from the most recently executed subprocess Nrrrrrrz/usr/sbin/userdelz--forcez--remove)ry)rr)r8rrr2r2r3rKs  zLinux.remove_accountcPCs ddddddddd d d d d ddddddddddddddddddd d!d"d#d$d%d&d'd(d)d*d+d,d-d.d/d0d1d2d3d4d5d6d7d8d9d:d;dd?d@dAdBdCdDdEdFdGdHdIdJdKdLdMdNdOdPgP}|ttdQdRdSdTgdU\}}}|dV}tdW||||D]"}||krtdX|||qdS)YNZ_aptZabrtZadmZapacheZavahiz avahi-autoipdbackupbinbindZcpanelZcpanelcabcacheZcpanelconnecttrackZcpaneleximfilterZcpaneleximscannerZ cpanelloginZcpanelphpmyadminZcpanelphppgadminZcpanelroundcubeZ cpanelrrdtoolZcpsesdaemonZdbusz Debian-eximZdovecotZdovenullftpZgamesZgnatsgopherZ haldaemonZhaltZ horde_sysuserZirclistZlpmailZmailmanZmailnullZmanZ messagebuszmhandlers-userZmysqlnamednewsZnginxZnobodyr$ZnscdZntpoperatorZpolkitdZpopuserZpostfixproxyZpsaadmZpsaftpZroundcube_sysuserrootZrpcZsaslauthr^ZsmmspZsmmtaZsshdZstatdz sw-cp-serversyncsyssyslogzsystemd-bus-proxyzsystemd-networkzsystemd-resolvezsystemd-timesyncZtcpdumptssuucpZuuiddZvcsaZ webalizerzwww-dataZcutz-d:z-f1z /etc/passwdzget current users z*exit_code: %s currentUserList: %s errs: %szRemoving user: %s)extendrLrrwrDdebugrZr)r8Z allowedUsersrrrZ user_listuserr2r2r3remove_non_default_users_sP     zLinux.remove_non_default_userscCstd|tt|dS)Nz+Removing user sudo permissions for %s at %s)rDrZr@rP)r8rGrr2r2r3 disable_adminszLinux.disable_admincCs tdSrl)r^)r8rr2r2r3shutdown_cleanszLinux.shutdown_cleanintervaltimeoutcCsd}|j|||dS)N configure_mtaintermediate_result)do_configure_mta)r8rrrrr2r2r3rszLinux.configure_mtarc Cs|d}td|j|||d}t|tr8|S|\}}}|dkrxtd|d|| d| |||fSz| |WnZt k r} zregexryrKcCs(|dkr d}dj||d}t||ddS)aUpdates file entries using the regex replacement string :param path: The path to the file :param regex: The regex replacement string for sed in format {regex_match}/{regex_replace} :param tag: Optional tag to describe operation N regex_replacezsed -i -r 's/{regex}/' {path})rr>Tr)formatr)r8r>rryrr2r2r3rszLinux.regex_replacerrrrKc Cs|\}}}|dkr |||fS||}||}dj|||d} |t| |\}}}|dkrj|||fSd}dj|||d} |t| |\}}}|dkr|||fSd} d} d} d j| | | |d } |t| |S) aUpdates the /etc/hosts file with the new hostname for the server. Need to use Child class methods. :param hostname: The new server hostname :param ip_addr: The IP address for the server :param op_name: The op that is running this function rz0^({ip_regex}).*$/\1 {hostname} {hostname_prefix})rrrz:^([a-f0-9]{4}:[a-f0-9]{4}:[a-f0-9]{4}:[a-f0-9]{4}::).*$/\1z'{ip_regex} {hostname} {hostname_prefix}z::1 localhostzU(localhost.localdomain localhost6 localhost6.localdomain6|ip6-localhost ip6-loopback)zM^{local_ip}((\s+{localhost})*\s+{localhost_hosts}).*$/{local_ip}\1 {hostname})local_iprlocalhost_hostsr)disable_cloud_init_hosts_updaterrrr HOSTS_PATH) r8rrrrrrrrrrrrr2r2r3r s@     zLinux.update_etc_hosts_hostnamecCs*tjtr&djtd}|t|dSdS)zIf the server/vm is managed by cloud-init, this function comments out 'update_etc_hosts' from the list of modules that run in the 'init' stage of boot. If this isn't done, the /etc/hosts changes may not survive a reboot. z$^.*- {init_module}$/#- {init_module}Z init_modulezdisable update_etc_hostsrzcloud-init not installedrrr=r>rNCLOUD_CONFIG_PATHr%CLOUD_CONFIG_UPDATE_HOSTS_INIT_MODULErr8rr2r2r3rIs  z%Linux.disable_cloud_init_hosts_updatecCs*tjtr&djtd}|t|dSdS)aIf the server/vm is managed by cloud-init, this function removes the comment from 'update_etc_hosts' in the list of modules that run in the 'init' stage of boot. This enables the cloud init service to configure /etc/hosts on first boot z$^.*- {init_module}$/ - {init_module}rzenable update_etc_hostsrrr r2r2r3enable_cloud_init_hosts_updateTs  z$Linux.enable_cloud_init_hosts_update)argsrKc Gsd}d}tjtrttddd}|}|dD]T}|drb|dj|d d  d 7}|d r6|d j|d d  d7}q6|dk r|W5QRSW5QRXd| dd|fS)zQReturns the contents of /etc/os-release file for the Operating System information get_os_inforrrutf-8encodingrzID=z NAME={name} rprqnamez VERSION_ID=zVERSION={version} )versionNFz!Unable to retrieve OS information) r=r>rNLINUX_OS_INFO_FILErsrurwrQrrxr)r8r rZos_infoZ os_info_filecontentsliner2r2r3r _s    zLinux.get_os_infoc Cs|ds dSd}t|dd\}}}td||tdddddzttWntk rbYnX|\}}} |d krd | || d fS| t d td dddt dtddddtdddd\}}} |d k| || d fS)Ncleanzgrep -qc set_hostname /etc/cloud/cloud.cfg || sudo sed -i 's/resizefs/&\n - set_hostname\n - update_hostname/' /etc/cloud/cloud.cfgTrz /etc/resolv.confzremove dns configz1rm -f /opt/thespian/director/thespian_system.log*Z snapshotClean) rrDrZrr=rO#CLOUD_CONFIG_PRESERVE_HOSTNAME_PATHFileNotFoundErrorr rrr) r8rr command_lineZdummy_exitcodeorrrrr2r2r3ros:   zLinux.snapshot_cleancCsdSrlr2)r8rr2r2r3 snapshot_prepszLinux.snapshot_prepc sd}td|tj|tjtjddddd}|j}tfdd|dd dDrbt|j t t d d|d d dDd d|d d dD}|d }t dd| D}|d?||d?dS)a`Get system memory utilization. We aim for our used calculation to match the "used" column of `free -m` as closely as possible. See used calculation for "Red Hat Enterprise Linux 7.1 or later" at https://access.redhat.com/solutions/406773. See also http://man7.org/linux/man-pages/man1/free.1.html#DESCRIPTION. zCcat /proc/meminfo |egrep "^(MemTotal|MemFree|Buffers|Cached|Slab):"z!Collecting memory utilization: %sT)rrshelluniversal_newlinesrcheckcsg|]}|jkqSr2)"MEMORY_UTILIZATION_SUPPORTED_UNITS)rTunitrr2r3rVsz1Linux._get_memory_utilization..NcSsg|]}|ddqS)Nr2rSr2r2r3rVscSsg|] }t|qSr2)intrSr2r2r3rVsrqMemTotalcSsg|]\}}|dkr|qS)r'r2)rTrUvr2r2r3rVs )Z memoryTotalZ memoryUsed)rDrZ subprocessrunPIPErrwrr4r"r_zipsumr)r8rresultr9totalfreer2rr3_get_memory_utilizations8     zLinux._get_memory_utilizationcCsd}td|tj|tjtjdddd}|jd}ttdd|}|}ttd d|}|}t t t |t |}d d t |d iS) Nz sar -u 1 1zCollecting cpu utilization: %sTr)rrr rr!rcSsd|kS)N%idler2rr2r2r3rrz,Linux._get_cpu_utilization..cSs |dS)NzAverage:rQr4r2r2r3rrZcpuUsedgY@r3) rwrDrZr*r+r,rnextfilterr_r-reversedfloat)r8rr/lineshead field_namesZ value_linevaluesr2r2r3_get_cpu_utilizations(  zLinux._get_cpu_utilizationcCs|dddgdS)z#Performs a yum update on the serveryumupdate-yz update system_run_yum_commandrr2r2r3 _yum_updateszLinux._yum_updatecCsdS)UConfigures the yum repo. Is a noop for all linux distros except CentOS6, which is EOLNr2rr2r2r3_yum_configure_reposzLinux._yum_configure_repoFuse_run_command_pipe)rHrKcOs.|d|kr|d=|r$t||St||S)a- A thin wrapper to runCommand or run_command_pipe for yum commands. Configures the yum repo before running the command :param use_run_command_pipe: Whether to use run_command_pipe instead of runCommand :return The runCommand/run_command_pipe result, as a tuple rH)rFrr)r8rHr rr2r2r3rCs  zLinux._run_yum_commandc Osn||||ddd|jgdj|jd\}}}dj|jd}||k}|dkoZ| } | |||dfS) auCreate a manifest file for the Panopta/Fortimonitor agent, add a repo file, and install panopta-agent/fm-agent using yum :param payload: payload containing customer_key (the key value to set in the manifest file) and template_ids (a csv string of the template ids to assign this server to) :return result of yum install operationr?rrAzinstall {agent_name}Z agent_namez)Installation of {agent_name} had an errorrinstall_panopta)_create_panopta_manifest_file_create_panopta_repo_filerCPANOPTA_AGENT_NAMErr) r8rr rrrr check_msgsilent_yum_install_errorsuccessr2r2r3rJs   zLinux.install_panopta customer_keyc Osvtdddgd\}}}d}t|ddd}|} W5QRXt|d dd6}| d D]$} d | krbd | krlqV|| d qVW5QRX|rV|d krz |}WnJtk r} z,d} t | d| dt | dfWYSd } ~ XYnXd} t| ddddj |d} t| dddd\}}}d}||k}|dkoB| }|| d|dfStd|dk| dddfS)z=Deletes panopta apt keys and upgrades panopta to fortimonitorapt-keydel61EE28720129F5F3upgrade_panopta/etc/apt/sources.listr rrwrzAddition to add panopta-agent/deb http://packages.panopta.com/deb stable mainNcustomer_key not foundFNecho 'debconf debconf/frontend select Noninteractive' | debconf-set-selectionszupgrade_panopta deb confTrzqcurl -s https://repo.fortimonitor.com/install/linux/fm_agent_install.sh | bash /dev/stdin -c {customer_key} -x -yrQzcurl upgrade_panopta%Installation of fm-agent had an errorrfm_agent installedzPanopta-agent not Installedrr)rrsrurwwrite_check_panopta_installed_get_panopta_customer_keyrMrDrErr\rencoderZ)r8rRr rrrrsources_file_panopta source_filer:rr]rHrrNrOrPr2r2r3rVsB    , zLinux.upgrade_panoptacOsLd}tj|stddj|d}t|ddd\}}}|dkrHtd |S) zY get the Panopta customer key from the agent manifest file and return it /etc/panopta-agent-manifestzrNrMrr)r8r rrer_rr2r2r3r`9s zLinux._get_panopta_customer_keycCs$|jdddddgddd\}}}|S) Nr?r installed|grep panopta-agentcheck panopta installedTrGrBr8rfrr2r2r3r_Gs   zLinux._check_panopta_installedcCstd|j}t|j|dS)N-customer_local_ops.operating_system.resources)rPANOPTA_YUM_REPO_TEMPLATErPANOPTA_YUM_REPO)r8repo_file_contentsr2r2r3rLNs zLinux._create_panopta_repo_filec Oszx|jdddddj|jdgdj|jddd \}}}|r|dd d |jgd j|jd\}}}|d krtd|jd|||dfWSnd dj|jdd}}}tj|j rt |j d }|dj|jd}|jdddddgddd \}}}|rJ|dd d dgd jdd\}}}|d krTtdd|||dfWSn d\}}}tjdrxt dd }|d}WnPt t fk r}z,d} t | d|dt|dfWYSd}~XYnX|d k|||dfS)z Deletes panopta-agent/fm-agent from the server and removes the panopta-agent/fm-agent manifest file :return result of yum uninstall operationr?rrgrhzgrep {agent_name}rIzcheck {agent_name} installedTrGremoverAzdelete {agent_name}rzfailed to remove %s agentFdelete_panoptaz#{agent_name} agent is not installedrrz) {agent_name} agent manifest file removedrizcheck panopta-agent installed panopta-agentzfailed to remove panopta-agent)rzpanopta-agent is not installedrrrdz$ panopta-agent manifest file removedz3Failed to unlink Panopta/Fortimonitor manifest fileN)rCrrMrDrrr=r>rNPANOPTA_MANIFEST_FILErOrBrCrEr\) r8r rrfrrrouts2r]rHr2r2r3rqTs`                 ,zLinux.delete_panoptacOsT|j}tj|s"tdj|ddj|d}t|ddd\}}}|dk|dd fS) z get the Panopta/Fortimonitor server key from the agent configuration file and return it :return a dictionary of the outs and errors z8Panopta/Fortimonitor config file: {agent_conf} not found) agent_confz:grep server_key {agent_conf} | sed s/'server_key\s*=\s*'//Zget_server_keyTrrF)rZ append_info)PANOPTA_CONFIG_FILEr=r>rNrMrr)r8r rrurrrrfr2r2r3get_panopta_server_keys zLinux.get_panopta_server_key)valid_resolversinvalid_resolversrKc OsX|}||ttdd|d\}}}|dkrB|ddt|}|dk|||dfS)a If the server has any of the listed invalid resolvers, replace them with the valid resolvers. Otherwise, leave the resolvers as-is. Currently the script takes only two valid and two invalid resolvers for updating. :param valid_resolvers: A list of valid dns nameservers to be added to the server :param invalid_resolvers: A list of invalid dns nameservers to be removed from the server :return: a dictionary of the outs and errors zupdate_dns_linux.shZupdate_dns_resolvers)Z script_fileryZscript_file_argsrz; z exit_code:update_invalid_resolvers)rrrr\r) r8rxryr rZ resolvers_argrrrr2r2r3rzs   zLinux.update_invalid_resolvers) file_pathrKcCs@|r<|r4z |Wq<tk r0Yqt k rd|} td|| d|d| |fYSXzt |Wn@t k rd|} td|| d|d| |fYSX|rXt |t krXdt } td|| d|d| |fSd ifS) a Helper method that performs validation for the 'write_out_file' op, before the file/directory is written out. :param op_name: Name of the op, should typically be 'write_out_file' :param name: Name of the file/directory to be created :param location: The directory/folder in which the file is to be created :param user_name: The user who will own the file/directory :param group: The group who will own the file/directory :param contents: The contents to be written out to the file :param is_file: Boolean parameter indicating if its a file or directory that is to be created :param exist_ok: Boolean parameter indicating if existence of the file is ok zFolder '{}' doesn't existz%s: %sFrrzFile '{}' already existszUser '{}' doesn't existzGroup '{}' doesn't existz&File contents exceeds size limit of {}T)rrNrrDrErr\rrrgrpgetgrnamlen MAX_FILE_SIZE) r8rrr~rrrr|rZ folder_locrr{r2r2r3Z__validate_write_files6     zLinux.__validate_write_file)rr{rrKc Csz2t|dkr|jddnt|}||Wn\ttfk r}z:td|t |t |t |}d| d||fWYSd}~XYnXdifS)a. Helper method that handles writing out to a file for the 'write_out_file' op. :param op_name: Name of the op, should typically be 'write_out_file' :param file_path: File path of the file to be written :param contents: The contents to be written out to the file rTrz%s: failed to write file %s: %sFrrN) rtouchbase64 b64decode write_bytesrBrCrDrEr\r)r8rr{rZdecoded_contentsr]rr2r2r3Z __write_files  (zLinux.__write_filec Csvz|jddWn\ttfk rl}z:td|t|t|t|}d|d||fWYSd}~XYnXdifS)z Helper method that handles creating a directory for the 'write_out_file' op. :param op_name: Name of the op, should typically be 'write_out_file' :param file_path: File path of the directory to be created Trz%%s: failed to create directory %s: %sFrrN)mkdirrBrCrDrEr\r)r8rr{r]rr2r2r3Z__make_directorys(zLinux.__make_directory)rr{permsrKc Csz|t|ddWnbtk rx}zD||td|t|t|t|}d|d||fWYSd}~XYnXdifS)a) Helper method that handles setting the file permissions for the 'write_out_file' op. :param op_name: Name of the op, should typically be 'write_out_file' :param file_path: File path of the directory to be created :param perms: Permissions for the new file )basez,%s: failed to change file permissions %s: %sFrrNT)rAr&PermissionError_Linux__cleanup_on_write_errorrDrEr\r)r8rr{rr]rr2r2r3Z__change_file_perms s (zLinux.__change_file_perms)rr{rrrKc Csztt|||Wnbtk rx}zD||td|t|t|t|}d|d||fWYSd}~XYnXdifS)aZ Helper method that handles setting the file ownership for the 'write_out_file' op. :param op_name: Name of the op, should typically be 'write_out_file' :param file_path: File path of the directory to be created :param user_name: Owning user of the file :param group_name: Owning group of the file z*%s: failed to change file ownership %s: %sFrrNT)shutilchownr\rrrDrEr)r8rr{rrr]rr2r2r3Z__change_file_ownerships  (zLinux.__change_file_ownershiprT) rr~rrrrr|rrKc Csd} || |||||||\} } | s,| | fSt||} |rJ|| | |n || | \} } | sf| | fS|| | |\} } | s| | fS|| | ||\} } | s| | fSd|dd| fS)a Op for writing out a regular file (with content) or creating a directory. Allows sets the file permissions and user/group ownership for the created file. :param name: Name of the file/directory to be created :param location: The directory/folder in which the file is to be created :param perms: The file permissons of the created file :param user_name: The user who will own the file/directory :param group_name: The group who will own the file/directory :param contents: The contents to be written out to the file :param is_file: Boolean parameter indicating if its a file or directory that is to be created :param exist_ok: Boolean parameter indicating if existence of the file is ok write_out_fileTrr)_Linux__validate_write_filer_Linux__write_file_Linux__make_directory_Linux__change_file_perms_Linux__change_file_ownershipr) r8rr~rrrrr|rrokr/r{r2r2r3r0s6  zLinux.write_out_file)rr~r|rKc sd}t|}tfddtDs.tdz|s>t|n t|WnFttt t fk r}z td |t |t ||W5d}~XYnXd| dd|fS) a- Op for deleting a file or a directory. :param name: Name of the file/directory to be deleted :param location: The directory/folder in which the file is to be deleted :param is_file: Boolean parameter indicating if its a file or directory that is to be deleted delete_filec3s|]}|VqdSrlr5)rTprefixrr2r3rasz$Linux.delete_file..zInvalid file/directory prefixz*{}: failed to delete file/directory {}: {}NTrr)rrVPS4_ALLOW_DELETE_PREFIXES RuntimeErrorr r=rprBrCrrrr\r)r8rr~r|rr{r]r2rr3rWs  zLinux.delete_file service_name is_systemd should_blockrKcCsd}|rB|r&t|jd|g|\}}}qXt|jdd|g|\}}}ntd|dg|\}}}|dkrtd||d||||fSd|d d |fS) a Op that restarts a service. Allows for restarting of a SysV service or a SystemD based service. :param service_name: Name of the service to be restarted :param is_systemd: Flag indicating if the service to be restarted is systemd unit or not :param should_block: Flag specific to systemd indicating if the we should wait for the completion of the command execution restart_servicerestartz --no-block /sbin/servicerz"failed to restart service: %s (%s)FTrr)r SYSTEMCTLrDrr)r8rrrZrc_tagrrrr2r2r3ros&      zLinux.restart_service)r> hash_typerKcCsh|d}t||gdj|d\}}}|dkrRtd||||td||||f|d}|dS)a& Get the hash for a given filepath and hash type, e.g. md5, sha1, sha256 :param path: The path of the file to be hashed :param hash_type: The type of hash to perform :return: The hashed file as a string :raises: RunTimeException if hashing fails r.z{hash_type} file)rrz"Failed to %s hash file %s: %s (%s) )rrrDrrrwrx)r8r>rrrrrZ outs_listr2r2r3_get_file_hashs  zLinux._get_file_hash)r>rKc CszTttj|}ttj|}ttj|}t|t|t|d}|WStk r}zt d|t|W5d}~XYnXdS)z Get the access, modify and change date and time of given file path :param path: The path of the file :return: Dict containing access, modify and change timestamps in format YYYY-MM-DD HH:MM:SS.xxxxxxxx TZ_OFFSET )accessmodifychangez Failed to file timestamps %s: %sN) r fromtimestampr=r>getatimegetmtimegetctimer\rBrDr)r8r>Z access_timeZ modify_timeZ change_timefile_timestampsr]r2r2r3_get_file_timestampsszLinux._get_file_timestamps)pathsrKc Cs6g}|d}|D]}|}d|i}t|}|srd|d<d|d<d|d<d|d<d|d <d|d <d|d <nd |d<d D]6}z|||||<Wq~tk rd||<Yq~Xq~z8||}|d |d <|d |d <|d |d <Wn.tk rd|d <d|d <d|d <YnX| |qd|i} | S)a6 Op for retrieving file info (if file exists) including the md5, sha-1 and sha-256 hashes, and access, change and modify dates :param paths: Comma-separated list of paths of files :return: Dict with filenames and their associated timestamps and hashes (if file present) rofileNameFrNNmd5sha1sha256rrrT)rrrfiles) rwrxrrNrrrrarBappend) r8rZout_listZ paths_listr>Zout_dictZfile_locrrretr2r2r3 get_file_infos@     zLinux.get_file_infocCsdddg}d|}d|}t|dd\}}}t|trB|d}t|trV|d}|d kr~td ||d |||d fS|d }|S)zr Op for returning rpm info on the vm :return: List of installed rpms with additional info zC"${HOSTNAME}|${VM_CONTAINER}|%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}|zD%{INSTALLTIME}|%{BUILDHOST}|%{DSAHEADER:pgpsig}|%{RSAHEADER:pgpsig}|z$%{SIGGPG:pgpsig}|%{SIGPGP:pgpsig}\n"rrzrpm -qa --queryformat TrrrzFailed to get rpm info: %s (%s)F get_rpm_infor) r?rrbytesdecoderDrrrw)r8Zquery_format_listZ query_formatrrrrrr2r2r3rs       zLinux.get_rpm_infopypi_urlrKc Os|d|jd|jd|j}||}t|tr:|S|\}}}|dkrfd|krfd|||dfS|\}}}|dkrd|||dfS|\}}}|dkrd|||dfSd S) z Op for installing the qemu agent on the vm :param pypi_url: The url for the pypi server where the package is located /-/rfr Nothing to doFinstall_qemu_agentconfigure_qemu_agentenable_qemu_agentN) DISTRO OS_VERSIONQEMU_PACKAGE_NAMErrr!r_configure_qemu_agent_enable_qemu_agent) r8rr r package_urlr/rrrr2r2r3rs"   zLinux.install_qemu_agentcCsd|j}t|dS)z5 Update the qemu agent configuration z1sed -i 's/^BLACKLIST_RPC=.*$/BLACKLIST_RPC=""/g' r)QEMU_AGENT_CONFIG_LOCr)r8rr2r2r3r s zLinux._configure_qemu_agentcCst|jddgdS)? Enable the qemu guest agent to start at boot. enableqemu-guest-agentr)rrrr2r2r3rszLinux._enable_qemu_agent)N)N)N)N)N)N)N)N)N)NN)N)N)N)N)N)rTF)T)TT)`r,r-r.ZDISK_UTILIZATION_PATH frozensetr"rrsrvrMrnrmrrrZOPERATING_SYSTEMZop_typerkrrr7r}r\RetryInstallCommandResultrrrr&rrrrrrrrboolrrrrrZRETRY_CONFIGURE_MTA_TIMEOUTZ"RETRY_CONFIGURE_MTA_RETRY_INTERVALr rr r rrrrrrrrrr r rrr2r>rDrFrCrJrVr`r_rLrqrwrrzrrrrrrrrrrrrrr RETRY_INSTALL_QEMU_AGENT_TIMEOUT!RETRY_INSTALL_QEMU_AGENT_INTERVALrrrrr<r2r2r:r3res      \        )   2$ (2   1  ' , recseZdZdZfddZeedfdd Zd dd Zeeee e eefd fd d Z e j dfeeeeeed ddZddeeedddZeedddZddddZedddZddddZddddZZS)!CentOSZCentoscstjtddSN)rk)r6r7rrr:r2r3r7szCentOS.__init__r~cs|tj|Srl)rFr6r)r8rrr:r2r3rszCentOS._installNc Cstd||||d|d}ttj|dD]}td|t|q4ttj|dD]}td|t|qdttj|dD]}td|t|qttj|d D]}td|t|qd }tj|st |d nPt |d d d}d| k} W5QRX| rBt |t jtdd ddn t|d t tj|dd d dd} | } t| dr| d} |} | s| dD]*} | dr| tddd} qW5QRXtdtt|t|dkrd}nd}d}t|D]\}}t tj|ddd d>} | dD],} | drP| |n| | dq4W5QRXt|| kr~qt tj|d|dd d0}|dd|dd d!t|d"gdW5QRXtjtj|d#rht tj|d#d d d}| }W5QRXt tj|d#dd d6}|dD]$} t|| krLn|| dq8W5QRX|d$k rt tj|d%|d&| t|ftd't|d$}td(d)||| qdS)*Nconfigure_ips: %s, %s, %srz/etc/sysconfig/network-scriptsz ifcfg-eth0:*zRemoving old config: %sz route-eth0:*zRemoving old routing: %sz ifcfg-lo:*z route-lo:*/etc/sysconfig/networkzGATEWAYDEV=eth0 r rrZ GATEWAYDEVFrz ifcfg-eth0rasciirzGATEWAY=zEndpoint Count: %sr$z DEFROUTE=yes z DEFROUTE=no rXZDEFROUTEz ifcfg-eth0:%szDEVICE=eth0:%dz ONBOOT=yeszBOOTPROTO=staticzIPADDR=zNETMASK=255.255.255.255z route-eth0Tz route-eth0:%sz7default via %s dev eth0 proto static src %s metric 100 zDefault route created for: %szConfigured %s to %s, gw %sz ifcg-eth0:%s)rDrZinsertglobr=r>r?rOrNrrsrurrrrrhasattrrrwrQrr\ enumerater^r)r8 vm_address addressesgatewayrcfgdiroldfileZnwconfZ nwconf_fileZgwdev base_cfg_filebase_cfggwrZdef_route_linedefault_route_createdifnumrethcfgZbase_route_fileZ base_router2r2r3configure_ip_files!s               &      zCentOS.configure_ip_filesrc sZt|||\}}}|dkr(|||fSd}d}||} dj| |||d} |t| |S)Updates the /etc/hosts file with the new hostname for the server :param hostname: The new server hostname :param ip_addr: The IP address for the server :param op_name: The op that is running this function rz #cloud-controlled; do not changez 127.0.0.1zC^{ip_regex}.*{comment}$/{local_ip} {hostname} {hostname} {comment})rcommentrr)r6rrrrr) r8rrrrrrrrrrr:r2r3rs  z CentOS.update_etc_hosts_hostname)r update_type nydus_versionrKc CsTt}td|||tjtjtjfkr:d|dddfS|jdddd gd |d \}}}|d krrd|dd dfS|tjkrt ddddd gd|d \}}}|d krd|dddfS|}|dkrd|dddfSd}t |rd |}n*t |rd |}nd|dddfS|d|jd|jd|} d }|tjkrl|jddd| gd ||d \}}}nv|tjkr|jddd| gd ||d \}}}nD|tjkr|jddd| gd ||d \}}}nd|dd dfS|d ksd!|krd|dd" |dfSt d#d$gd%|d \}}}|d krBd|dd&d'fSd(|||dfS))aD Op for updating pyinstaller based nydus package :param pypi_url: The url for the pypi server where the package is located :param update_type: The type of update to perform, one of "reinstall", "upgrade" or "downgrade" :param nydus_version: The version of nydus to use for the update z#update_type: %s , nydus_version: %sFrrzInvalid operation update_nydusr?rrgznydus-executorz#Check for pyinstaller based package)rcrz:Pyinstaller based nydus package is currently not installedrpmz-qz --queryformatz%{VERSION}-%{RELEASE}zget nydus versionz(Error determining existing nydus versionNz9Nydus version is a required parameter but wasn't providedznydus-executor-{}.x86_64.rpmznydus-executor-{}.rpmzInvalid nydus versionrrfr@rAzUpdate nydus to version {}r+r*zUnsupported nydus update actionzAThe same or higher version of nydus-executor is already installedzNydus update failed {}Z systemctlz daemon-reloadzreload systemd unit filesz"Error reloading systemd unit filesZget_nydus_versionT)rdrDrZr(r1r0r/rrCr SEMVER_RGXrrMAJOR_ONLY_RGXrr) r8rrrrcrrrZnydus_pkg_namerr2r2r3rs                  zCentOS.update_nydusrQ)rRrKc sj|dsd|dddfS|s6d|dddfSz |WnPttfk r}z.tdd|dd t |dfWYSd }~XYnXz | }WnJt k r}z,d }t |d|d t |dfWYSd }~XYnXd j|d }t |ddd\}}|dkr(d||dfSdddg} tfdd| D} |dkoV| } | |d|dfS)z@Deletes panopta repo config and upgrades panopta to fortimonitorrrTrrz'Panopta is not installed, nothing to dorVFzFailed to install python3z&Error while deleting panopta repo filez&Failed to delete panopta repo file: {}NrZzcurl -s https://repo.fortimonitor.com/install/linux/fm_agent_install.sh | \ sudo bash /dev/stdin -c {customer_key} -x -yrQrrr\z No suitable python version foundz&Agent installation must be run as rootc3s|]}|kVqdSrlr2)rTmsgrr2r3rsz)CentOS.upgrade_panopta..r])_check_package_installedrinstall_python3_remove_panopta_reporCrBrDrZrr\r`rMrErr) r8rRr rr]rHrrrZerr_check_msgsrOrPr2rr3rVs>      , zCentOS.upgrade_panopta)packagerKcCs&tdd\}}}||dkr"dSdS)zCheck if package is installedzsudo yum list installedz"Check whether package is installedrTF)rr)r8rrfrr2r2r3r s  zCentOS._check_package_installedrcCs2ztdWntk r,tdYnXdS)zRemove panopta repoz/etc/yum.repos.d/panopta.repozPanopta repo file not foundN)r=rprrDrZrr2r2r3rszCentOS._remove_panopta_repoc Cstddgddd\}}}|dkr,d|kr,dSz |Wn<ttfk rt}ztdt|WYd Sd }~XYnX|d d d dgd\}}}|dkrtd||d Sz | Wn<ttfk r}ztdt|WYd Sd }~XYnXdS)z(Install python3 if not already installedZpython3z-VzCheck Python versionTrrz Python 3.z!Failed to add hfs_common repo: %sFNr?rrAzInstall python3z"failed to install python3: %s (%s)z$Failed to remove hfs_common repo: %s) r_add_hfs_common_reporCrBrDrZr\rCr_remove_hfs_common_repo)r8rrrfr]Z exit_code_yumZouts_yumrr2r2r3rs*    zCentOS.install_python3cCst}tdd}t||dS)z&Add gpg key and hfs common repo configrlzhfs_common.repoN)HFS_COMMON_YUM_REPOrr)r8Z repo_fileror2r2r3r6s zCentOS._add_hfs_common_repocCsttdS)z"Remove hfs_common repo and gpg keyN)r=rprrr2r2r3r?szCentOS._remove_hfs_common_repo)N)r,r-r.rr7r\rrrrr&rr(r1rrrrVrrrrrrr<r2r2r:r3rs(  b   I&  rcsreZdZdZdZedddZfddZdd d Zddd d Z de e e edfdd Z e dddZZS)CentOS66zqemu-guest-agent-0.rpmrcOsd|dddfS)NFrrz Not supportedr)r)r8r rr2r2r3rHszCentOS6.update_nyduscsJtj||tdddgd\}}}|dkrFtd||d||gfSdS)Nrnetworkrnetwork restartr failed to configure ips: %s (%s)Fr6rrrDrr8r rrrrr:r2r3 configure_ipsKs  zCentOS6.configure_ipsNc Csd}|d}|d}td|ddd\}}}|dkrFd ||||fSz td tjtd d |d d WnLtk r} z.tdt | d ||t | |fWYSd} ~ XYnX| |||\}}}|dkrd ||||fS||||S)Nrrrz hostname rTrrFrHOSTNAMEz HOSTNAME=%s rz0Failed to update /etc/sysconfig/network file: %s) rrrrrrrrDrr\r) r8rrrrrrrrr]r2r2r3rVs,,zCentOS6.change_hostnamecCst}tdd}t||dS)rErlzcentos6_base.repoNCENTOS6_YUM_REPOrrr8Z yum_repo_fileror2r2r3rFrs zCentOS6._yum_configure_repoFTrcst|||S)a CentOS6 implementation of restart_service op that defaults to SysV service :param service_name: Name of the service to be restarted :param is_systemd: Flag indicating if the service to be restarted is systemd unit or not :param should_block: Flag specific to systemd indicating if the we should wait for the completion of the command execution )r6r)r8rrrr:r2r3rzs zCentOS6.restart_servicecCstdddgdS)rz/sbin/chkconfigzqemu-gaonrrrr2r2r3rszCentOS6._enable_qemu_agent)N)FT)r,r-r.rrrrrrrFr\rrrrr<r2r2r:r3rDs  rcs@eZdZdZdZfddZddddZd fd d ZZS) CentOS77zqemu-guest-agent-2.rpmcsJtj||tdddgd\}}}|dkrFtd||d||gfSdS)NrjrrrrrFrrr:r2r3rs  zCentOS7.configure_ipsNrcCst}tdd}t||dS)zUConfigures the yum repo. Is a noop for all linux distros except CentOS7, which is EOLrlzcentos7_base.repoNrrr2r2r3rFs zCentOS7._yum_configure_repocs|t|||Srl)rFr6r)r8rrrr:r2r3rszCentOS7.configure_mta)NN) r,r-r.rrrrFrr<r2r2r:r3rs  rc@seZdZdZdZdS)CentOS88qemu-guest-agent-6.rpmNr,r-r.rrr2r2r2r3r sr c@seZdZdZdZdZdS) AlmaLinux8Z Almalinuxr r N)r,r-r.rrrr2r2r2r3r sr c@seZdZdZdZdS) AlmaLinux99zqemu-guest-agent-7.rpmNr r2r2r2r3rsrcsFeZdZdZdZdZdZeZdZdZ dZ e ffdd Z d d Z d d ZddZddZd1ddZdZd2fdd ZddZdZdZeeedddZdd Zd!d"Zeeeeeeefd#fd$d% Zeeeeeeefd&d'd(Zeeeefd)d*d+Z eeeefd)d,d-Z!dZ"dZ#ee#e"dee$d.d/d0Z%Z&S)3Debian/bin/systemctlrrirhz(https://repo.fortimonitor.com/deb-stablez.https://repo.fortimonitor.com/fortimonitor.pubrgcstj|ddSrr5rmr:r2r3r7szDebian.__init__cCs&tj|r"td|t|dS)Nz Removing: %s)r=r>rNrDrZrO)r8Zfull_file_namer2r2r3 remove_files  zDebian.remove_filec Csd}d}|d}tj|s&t|d|}tj|dtjd}|\}}t d||tj |rt |dd }t |}W5QRX|S) Nrrz /mnt/cdromz#/openstack/latest/network_data.jsonzsudo /bin/mount /dev/sr0 T)rrz/mount network data drive. stdOut: %s stdErr: %srr)r=r>rNrr*Popenr, communicaterDrZisfilersjsonload) r8dataZ mountpathZ networkconfigrprrZncr2r2r3mount_network_settingss"    zDebian.mount_network_settingscCsd}|dD]}|dr|d}qtdt|t|dkr|}z,|ddddd}td t|Wntk rtd YnXt|S) Nrrrrrq Gateway: %sZnetworksrZrouteszUpdated Gateway: %szCould not auto-detect gateway) rwrxrQrDrr\r TypeErrorcritical)r8rrrrr2r2r3 get_gateways zDebian.get_gatewaycCsd}|dD],}|dr|dd}d|}qtdt|t|dkr|}z,ddd|d D}td t|Wntk rt d YnX|S) Nrrrzdns-nameserversrqrznameservers: %scSs g|]}|ddkr|dqS)typeZdnsaddressr2)rTsr2r2r3rVs z*Debian.get_nameservers..ZserviceszUpdated nameservers: %sz!Could not auto-detect nameservers) rwrxrQr?rDrr\rrr)r8rnsrrr2r2r3get_nameserverss  zDebian.get_nameserversNcCstd|||d}d}ttj|dD]}||q,|tj|dd}tjtj|drttj|ddd d } | } W5QRXnNtjd rtd dd d } | } t d | krd }W5QRXnt dd} |r@t ddd gdd dtd dd d } | dd gW5QRXt ddd gdd d|} | sT|| } tdt | || } ||d}t|D] \}}t || krttj|dt |dd d J}| dd|d|dt | dt |dd t |d!gdW5QRXtd"t ||d k rt|dd d V}td#|| dd$d%d&t | d't |d(d)gdt|d*d }W5QRXqdS)+Nrz/etc/network/interfaces.d"/etc/network/if-up.d/update-routesz hfs-*.cfgz51-public-ips.cfgFz50-cloud-init.cfgr rrz/etc/network/interfacesz"source /etc/network/interfaces.d/*Tz$Default Networking config not found!rrZchattr-iz&allow write to /etc/network/interfacesrarz+iz'remove write to /etc/network/interfacesrz hfs-%s.cfgrXz auto eth0:%dziface eth0:%d inet staticz dns-nameservers z address z netmask 255.255.255.255z up route add -net z! netmask 255.255.255.255 dev eth0zConfigured %s to eth0z Writing: %sz #!/bin/bashzip route del defaultzip route replace default via z dev eth0 src z proto static metric 1024zexit 0i)rDrZrr=r>r?rrNrsrur\rrr^rrr#rrrA)r8rrrrrZ routefilerZupdate_interface_flagrrZbase_cfg_file_wrr"rrrrZroutecfgr2r2r3rs       "      zDebian.configure_ip_filesr$cs2|ds dStj|jr&t|jt|S)Nr)r=r>rNUPDATE_ROUTES_PATHrOr6rrr:r2r3rLs  zDebian.snapshot_cleancOs`|j||tdddddgdddtd d d gd \}}}|d kr\td||d||gfSdS)NipaddrflushdevZeth0zflush old interface dataTrrrZ networkingrrrF)rrrDrrr2r2r3rUs   zDebian.configure_ipsrrrc Ostdddgdj|jd\}}}d}t|ddd }|} W5QRXt|d dd .}| d D]} d | krnq`|| d q`W5QRXd } tj | st | ttj | dd dd (}d ddj|j dg} || W5QRXtdj|j ddj|jddd\}}}|dkr&d||gfS||tdddgdj|jd\}}}|dkrfd||gfSd} t| dj|jdddtddd|jgdj|jd\}}}d|k}|dko| |||dfS) a Create a manifest file for the fm-agent, update the apt-key with fm-agent, update apt-get, and install fm-agent using apt-get :param payload: payload containing customer_key (the key value to set in the manifest file) and template_ids (a csv string of the template ids to assign this server to) :return result of apt-get install operationrSrTrUzinstall {agent})ZagentrWr rrrXrrYz/etc/apt/sources.list.dz fm_agent.listz## Addition to add fm-agentzMdeb [signed-by=/usr/share/keyrings/fortimonitor.pub] {agent_repo} stable main)Z agent_repozNwget -O - {publish_url} | tee /usr/share/keyrings/fortimonitor.pub > /dev/null)Z publish_urlTrrFapt-getr@z --fix-missingr[rrAz%Installation of fm_agent had an errorrJ)rrrMrsrurwr^r=r>rNrr?PANOPTA_STABLE_RELEASEPANOPTA_PUBLISHER_URLrKr)r8rr rrrrrbrcr:rZsources_file_pathZsources_file_additionrZsilent_install_errorr2r2r3rJcs`               zDebian.install_panoptac Os`ztdd\}}}tdd\}}}|rdtddddgd \}}}|d krdtd d |||d fWS|rtdddd gd\}}}|d krtdd |||dfWSn d\}}}tjdrtdd }|d}tjdrtdd }|d}WnPt t fk rH}z,d} t | d |dt |dfWYSd}~XYnX|d k|||dfS)z delete panopta/fortimonitor from the server and removes the panopta/fm_agent manifest file :return result of apt uninstall operation dpkg --list | grep panopta-agentrjzdpkg --list | grep fm-agentzcheck fm-agent installedr,purgerArrzdelete panoptarzfailed to remove panopta agentFrizdelete fm-agentzfailed to remove fm-agent)rz!panopta/fm agent is not installedrrrdz$ panopta agent manifest file removedrgz fm-agent manifest file removedz/Failed to unlink panopta/fm agent manifest filerrrqN) rrrDrrr=r>rNrOrBrCrEr\) r8r rrfrrtrrr]rHr2r2r3rqsN               ,zDebian.delete_panoptacCstdd\}}}|S)Nr/rj)rrkr2r2r3r_s  zDebian._check_panopta_installedrc s`t|||\}}}|dkr(|||fS||}d}||} dj| |||d} |t| |S)rrz 127.0.1.1z6^{ip_regex}.*$/{local_ip} {hostname} {hostname_prefix})rrrr)r6rrrrrr) r8rrrrrrrrrrr:r2r3rs   z Debian.update_etc_hosts_hostnamercCs6td||f}d|}t|d|d|ddS)rrrrTr)rrrrarr2r2r3rs zDebian._run_change_password_cmdrcCsdS)z Make sure that the 'python' executable runs python3. No install needed by default. :returns: A runCommand result tuple rr2rr2r2r3_install_python_is_python3sz!Debian._install_python_is_python3cCstdd|ddgdS)rrrz-sz /bin/bashrrrr2r2r3rszDebian._run_add_user_commandrcOsr||j}t|tr|S|\}}}|dkrFd|krFd|||dfS|\}}}|dkrnd|||dfSdS Install qemu guest agent on the vm :param pypi_url: The url for the pypi server where the package is located rrFrrNrrrr!rrr8rr rr/rrrr2r2r3r s   zDebian.install_qemu_agent)N)N)'r,r-r.rrZPANOPTArvrMr-r.rsrr7rrrr#rr'rrZRETRY_INSTALL_PANOPTA_TIMEOUTZRETRY_INSTALL_PANOPTA_INTERVALr rJrqr_r\rr&rrr1rrrrrr<r2r2r:r3rsJ L    >+   rcsDeZdZdZdZdZdZedffdd Ze e ddd Z Z S) Debian8rzqemu-guest-agent_2.debr rcst|dSrlr5rmr:r2r3r7%szDebian8.__init__rcOs|d|jd|jd|j}td|gd\}}}|dkrPd|||dfStdd |jgd \}}}|dkrd|||d fS|\}}}|dkrd|||d fSd S) r3rrfZwgetzDownload qemu_guest_agentrFZdownload_qemu_agentZdpkgr%rrN)rrrrrr)r8rr rrrrrr2r2r3r(s"  zDebian8.install_qemu_agent) r,r-r.rrrrrr7r\rrr<r2r2r:r3r6s r6c@s eZdZdS)Debian10Nr,r-r.r2r2r2r3r7<sr7c@s$eZdZeeeefdddZdS)Debian11rcCs |dSzq Make sure that the 'python' executable runs python3 :returns: A runCommand result tuple zpython-is-python3rrr2r2r3r1Asz#Debian11._install_python_is_python3Nr,r-r.rr&r\r1r2r2r2r3r9@sr9c@s eZdZdS)Debian12Nr8r2r2r2r3r=Isr=csLeZdZdZdZdZedffdd Zeeede e ddd Z Z S) Ubuntu1604rrrcst|dSrlr5rmr:r2r3r7RszUbuntu1604.__init__rrcOsr||j}t|tr|S|\}}}|dkrFd|krFd|||dfS|\}}}|dkrnd|||dfSdSr2r4r5r2r2r3rUs   zUbuntu1604.install_qemu_agent) r,r-r.rrrrr7r r\rrr<r2r2r:r3r>Ms  r>c@s$eZdZeeeefdddZdS) Ubuntu2004rcCs |dSr:r;rr2r2r3r1isz%Ubuntu2004._install_python_is_python3Nr<r2r2r2r3r?hsr?c@s eZdZdS) Ubuntu2204Nr8r2r2r2r3r@qsr@c@s eZdZdS) Ubuntu2404Nr8r2r2r2r3rAusrA)jrrrrrloggingr=rrerrr*renumrpathlibrtypingrrrrr r collectionsr r Zcryptography.x509r Zimportlib_resourcesrZcustomer_local_opsrrrZcustomer_local_ops.exceptionsrZ#customer_local_ops.operating_systemrZ3customer_local_ops.operating_system.package_managerrrrrZcustomer_local_ops.util.executerrrrZcustomer_local_ops.util.helpersrrrrZcustomer_local_ops.util.retryr r!r" getLoggerr,rDr>r?seprtr@rLrRrrrrrrrcompilerrrrrr\r(rr4rIrPrYr^rdrerrrr r rrr6r7r9r=r>r?r@rAr2r2r2r3s                /Hi  __pycache__/package_manager.cpython-38.pyc000064400000014136147205360270014504 0ustar00U af@sddlmZmZmZddlZddlmZddlmZddl m Z ddl m Z ddl mZmZdgZGd d d ZGd d d eZGd ddeZGdddeZdS))DictListOptionalN)rmtree)Path) read_text) create_file) runCommandRunCommandResultZ61EE28720129F5F3c@sPeZdZdZeeeefdddZddeeedddZ edd d Z dS) PackageManagerNreturncCsdS)z{Environment variables to send to subprocess. This version copies the environment from the parent process. Nselfrrd/opt/nydus/tmp/pip-target-53d1vnqk/lib/python/customer_local_ops/operating_system/package_manager.py_envszPackageManager._envtagpackagesrr cGs@|s td|jt|}|dkr.dt|}t|||dS)zInstall one or more packages. :param *pkg_spec: one or more specifications of packages to install :param tag: optional identifier included in logs :returns: result of installation command as (exit_code, stdout, stderr) z At least one package is requiredNz install %senv) ValueError INSTALL_ARGSliststrr r)rrrcmdrrrinstalls  zPackageManager.installcCsdS)zUpdate local package indices from remote sources. Not all package managers need this, so this version does nothing and returns a fake success result. :returns: result of update command as (exit_code, stdout, stderr) )rrrrrrrupdate_indices+szPackageManager.update_indices) __name__ __module__ __qualname__rrrrrr rr rrrrr sr c@s>eZdZdddgZeeeefdddZedddZ d S) Aptapt-getr-yr cCstj}d|d<d|d<|S)zEnvironment variables to send to subprocess. We set DEBIAN_FRONTEND to ensure user is not prompted to answer any questions during installation. ZnoninteractiveZDEBIAN_FRONTEND1Z$APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE)osenvironcopy)rrrrrr9s zApt._envcCsLtD]&}ddddd|g}t|d|dqdd d d d g}t|d |dS)Nzapt-keyZadvz --keyserverzhkp://keyserver.ubuntu.com:80z --recv-keyszapt-key get public keysrr%updater&z-ozAcquire::ForceIPv4=truezapt-get update)APT_PUBLIC_KEYSr r)rkeyrrrrr Ds zApt.update_indicesN) r!r"r#rrrrrr r rrrrr$6s  r$cseZdZddddgZdZddgZdZd Zed d d Z d dddZ d dddZ d dddZ d dddZ edfdd Zd deeedfddZZS)AptEOLz /tmp/tmp_aptz/tmp/tmp_apt/source.list.dz/tmp/tmp_apt/tmp_apt_cachez/tmp/tmp_apt/tmp_apt_listsz/tmp/tmp_apt/source.listZDebian8Z Ubuntu1604z/etc/apt/apt.confz-customer_local_ops.operating_system.resources)os_namecCs ||jkrtd|||_dS)Nz2Invalid Linux distributive name was provided - %s.)LINUX_EOL_DISTROSrr/)rr/rrr__init__Ys  zAptEOL.__init__Nr cCsH|jD]}t|jdddq|jd}t|j|}t|j|dS)z Add folder structure for temporary APT config Fill source.list file with mirrors for provided Linux distributive :returns: None T)parentsexist_okz _mirrors.repoN) TMP_DIRSrmkdirr/lowerr RES_PACKAGErTMP_SOURCE_LIST)rZdir_pathZ mirrors_listZmirrors_list_contentrrr_add_tmp_source_list_dirs_s z AptEOL._add_tmp_source_list_dirscCst|jd}t|j|dS)zN Fill apt.conf file with a new config :returns: None ztmp_apt_config.repoN)rr7r APT_CONF_PATH)rZapt_config_contentrrr_add_tmp_apt_configps zAptEOL._add_tmp_apt_configcCst|jdS)z> Delete apt.conf file :returns: None N)r(remover:rrrr_clear_apt_conf{szAptEOL._clear_apt_confcCst|jddS)zT Delete all tmp folders used for apt update :returns: None rN)rr4rrrr_clear_tmp_apt_foldersszAptEOL._clear_tmp_apt_foldersc sZz||Wn:tttfk rN}ztdt||W5d}~XYnXtS)z Run both _add_tmp_source_list_dirs and _add_tmp_apt_config methods :returns: run update_indices method for super class if no errors; otherwise raise RuntimeError z>An error occurred during file operation with tmp source list: N) r9r;PermissionErrorIOErrorOSError RuntimeErrorrsuperr )rex __class__rrr s  $zAptEOL.update_indicesrrc sftj|d|i}z||Wn:tttfk r`}ztdt||W5d}~XYnX|S)Nrz.An error occurred during clear apt.conf file: ) rCrr=r>r?r@rArBr)rrrresultrDrErrrs $zAptEOL.install)r!r"r#r4r8r0r:r7rr1r9r;r=r>r r r __classcell__rrrErr.Ms  r.c@seZdZdddgZdS)YumZyumrr&N)r!r"r#rrrrrrIsrI)typingrrrr(shutilrpathlibrZimportlib_resourcesrZcustomer_local_ops.util.helpersrZcustomer_local_ops.util.executer r r,r r$r.rIrrrrs    'X__pycache__/windows.cpython-38.pyc000064400000057456147205360270013125 0ustar00U afPs@sddlmZddlmZmZmZmZmZddlZddl Z ddl Z ddl Z ddl m Z mZmZddlmZddlmZddlmZmZddlmZmZe eZeeed d d ZGd d d eZGdddeZ GdddeZ!GdddeZ"dS))Path)AnyDictListUnionTupleN) NydusResultOpsOpType) DecryptError)POWERSHELL_PATH)b64strexecute) runCommandrun_powershellscriptargskwcOstjt|f||SN)rrun_powershell_filer rr\/opt/nydus/tmp/pip-target-53d1vnqk/lib/python/customer_local_ops/operating_system/windows.pyrsrc@s(eZdZdZejZdZdZdZ ddZ e e e fe eedddZdXee efeed d d Ze e ed ddZedddZddZee efedddZdYddZdZddZd[ddZd\ddZd]d d!Zd^d"d#Zd$d%Zd_ee efd&d'd(Zd`d)d*Z dad+d,Z!d-d.Z"d/d0Z#d1d2Z$e e%e&e e fd3d4d5Z'e e%e&e e fd6d7d8Z(e e%e&e e fd9d:d;Z)eddd?Z+d@dAZ,e-e e-e edBdCdDZ.eedEdFdGZ/e&eedHdIdJZ0e&eedHdKdLZ1e dMdNdOZ2e dMdPdQZ3eedRdSdTZ4e ee efdUdVdWZ5d S)bWindowszC:\\zvirtio-win.isoz devcon.execCsd}t|ddS)zReturn this VM's tag.z $s = Get-childitem -Path Cert:\LocalMachine\My\ | Where-Object {$_.Issuer -match "Nydus Customer Services"} | Select-Object -ExpandProperty Subject $s = $s -replace "(CN=)(.*?),.*",'$2' $s Z get_vm_tagT)r)selfZcommand_get_cnrrr _get_vm_tagszWindows._get_vm_tag) script_fileop_namerreturncKs&|d|}|jt||f||fS)aRun a PowerShell script and return a Nydus result for the outcome. :param script_file: PowerShell script name, no path (must be in operating_system/powershell/) :param op_name: name of the Nydus operation :param kw: additional keyword arguments to pass to run_powershell_file :returns: standard CLO Nydus result with success based on script exit code (see build_result_from_cmd_output) tag)popZbuild_result_from_cmd_outputr)rrrrrrrr_run_powershell_op's   zWindows._run_powershell_opN)payloadunusedrc Cs|ddrtdd}|d}|d}z||}Wn<tk rr}zd||j|j|fWYSd}~XYnXt|}td||d g|d d \} } } | |d } | d k|| | |fS)zCreate a user that can access this server over Remote Desktop. :param payload: dictionary containing username and encrypted_password :returns: result of the operation Zfail_if_existsFz#fail_if_exists:True not implementedadd_userusernameencrypted_passwordNz add_user.ps1Remote Desktop UsersTscript_file_argsstdinquietr) getNotImplementedErrordecryptr build_result_dictoutserrsr rreplace) rr"r#rr%r&passwordex password_b64 exit_coder1r2rrrr$4s& ,  zWindows.add_user)r%grouprcCs|jdd||gdS)zAdd a user to a group. :param username: name of user to add to the group :param group: user will be added to this group :returns: result of the operation zadd_user_to_group.ps1add_user_to_groupr)r!)rr%r8rrrr9Ns zWindows.add_user_to_group)rcCs,d}t|d\}}}|r(||kr(dSdS)zCheck if the specified user needs to log out :param username: name of user to check :returns: True or False z query userZget_active_sessionsTF)rlower)rr%Zget_sessions_command_r1rrr_user_needs_logoutZs zWindows._user_needs_logoutcCsVd}||}|r$d|dd|fSd||f}t||\}}}|dk||||fS)N remove_userFzUser is still active.z8User needs to perform logout before user can be removed.zEnet user %s /DELETE ; Remove-Item -Path "C:\Users\%s" -Recurse -Forcer)r>r0r)rr%rZis_user_activecommandr7r1r2rrrr?gs  zWindows.remove_user)r"rc Csd}|d}|d}z||}Wn<tk r^}zd||j|j|fWYSd}~XYnXt|}td||g|dd\}} } | |d } |d k|| | |fS) zChange a user's password. :param payload: dictionary containing username and new encrypted_password :returns: result of the operation change_passwordr%r&FNzchange_password.ps1Tr(r,r)r/r r0r1r2r rr3) rr"rr%r&r4r5r6r7r1r2rrrrAvs",  zWindows.change_passwordcCsd}|||S)N configure_mta)do_configure_mta)rr"r#rrrrrBszWindows.configure_mtacCsrtd|tddd|dgd|\}}}d}|dkrdd |ksFd |krRtd nd ||||fS||||S) NzRemoving user sudo permissions for %s via administrators group disable_adminzadd RDC AccessrDrEr'rGzremove Admin AccessrFz/deleterrIrJrKFrL) rr%r#rcommandspurposecmdr7r1r2rrrrOs  zWindows.disable_admincCs.d}d}t|d\}}}|dk||||fS)Ndisable_all_adminsa@ net user tempHfsAdmin /delete $usersToKeep=@("admin", "Administrator", "cloudbase-init", "DefaultAccount", "Guest", "IME_ADMIN", "IME_USER", "IUSRPLESK_atmail", "IUSRPLESK_horde", "IUSRPLESK_smwebmail", "IUSRPLESK_sqladmin", "IUSR_FS_PUBLIC", "IUSR_FS_UNLISTED", "IWAM_FILESHARING", "IWAM_plesk(default)", "IWAM_sitepreview", "nydus", "Plesk Administrator", "psaadm") Get-CimInstance -ClassName win32_group -Filter "name = 'administrators'" | ` Get-CimAssociatedInstance -Association win32_groupuser | ` %{ if($usersToKeep -notcontains $_.name) { $userName=$_.name "Adding user to 'Remote Desktop Users' group: $userName" net localgroup "Remote Desktop Users" $userName /add "Removing user from 'Administrators' group: $userName" net localgroup "administrators" $userName /delete } }zDisable All Adminsrrr0)rr#rr@r7r1r2rrrrSs zWindows.disable_all_adminscCstdSr)r.)rr#rrrshutdown_cleanszWindows.shutdown_cleanc CsP|ds dSd}d}t|d}|d|d|d}}}|dk||||fS)Ncleansnapshot_cleana start-transcript -path "C:\Windows\temp\snapClean.txt" $dirs = "C:\\thespian\\director*","C:\\nydus\\log*" Foreach ($dir in $dirs) { $programFolder=try{((Get-ChildItem $dir) | Sort-Object lastwritetime -desc)[0].fullname}catch{"NA"}; "Removing Logs: $($programFolder)\*.log" if(Test-Path $programFolder){ remove-item "$($programFolder)\\*.log" -force -ErrorAction SilentlyContinue; } } $name = 'testaccount' $acct = New-Object Security.Principal.NTAccount($name) $sid = $acct.Translate([Security.Principal.SecurityIdentifier]).Value Get-WmiObject Win32_UserProfile -Filter "sid='${sid}'" | ForEach-Object { $_.Delete() } "Removing Bootscript" $bootScript="C:\\Windows\\Temp\\bootscript.ps1"; if(Test-Path $bootScript){ remove-item $bootScript -force} #clear any non-default users $usersToKeep=@("admin", "Administrator", "cloudbase-init", "DefaultAccount", "Guest", "IME_ADMIN", "IME_USER", "IUSRPLESK_atmail", "IUSRPLESK_horde", "IUSRPLESK_smwebmail", "IUSRPLESK_sqladmin", "IUSR_FS_PUBLIC", "IUSR_FS_UNLISTED", "IWAM_FILESHARING", "IWAM_plesk(default)", "IWAM_sitepreview", "nydus", "Plesk Administrator", "psaadm") Get-WmiObject -query "select Name from Win32_UserAccount where LocalAccount='True'" | ?{$usersToKeep -notcontains $_.name} | %{ $userName=$_.name $cn = [ADSI]"WinNT://$($env:Computername)" "Removing user: $userName" try{ $cn.Delete('User',"$userName") }catch{ $_.Exception.Message } } #clear event logs: function Clear-All-Event-Logs ($ComputerName="localhost"){ $Logs = Get-EventLog -ComputerName $ComputerName -List | ForEach {$_.Log} $Logs | ForEach {Clear-EventLog -Comp $ComputerName -Log $_ } Get-EventLog -ComputerName $ComputerName -List } Clear-All-Event-Logs #remove old cloud-init logs remove-item "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\*.log" #Disable password policy secedit /export /cfg c:\secpol.cfg (gc C:\secpol.cfg).replace("PasswordComplexity = 1", "PasswordComplexity = 0") | Out-File C:\secpol.cfg secedit /configure /db c:\windows\security\local.sdb /cfg c:\secpol.cfg /areas SECURITYPOLICY rm -force c:\secpol.cfg -confirm:$false #add hostname setting $cloudconfpath="C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init.conf" (gc $cloudconfpath) -replace "plugins=cloudbaseinit.plugins.windows.removeqxl.RemoveQXLPlugin",` "plugins=cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin,cloudbaseinit.plugins.windows.removeqxl.RemoveQXLPlugin"` | sc -path $cloudconfpath stop-transcript zsnapShot CleanuprrT) rr"r#rr@rr7r1r2rrrrWsG zWindows.snapshot_cleanc Csd}|d}d}z||d|WnJtk rn}z,td||d|dt||fWYSd}~XYnXd|}t|||d|S) N snapshot_prepZinternalAddresszC:\Windows\Tempz\snapShotIP.txtz&Error writing snapShotIP.sh for %s: %sFr,zsnapShotIP.txt written for {0}) write_fileIOErrorrM exceptionr0strformatrN)rr"ripZ installLocr5Zsuccess_messagerrrr[0s,  zWindows.snapshot_prep)intermediate_resultc Cs|d}td||dkr,|dd|Sd}tdd|dd d |d gd \}}}|d krjd||||fStdd|ddd dd gd\}}}|d krd||||fS||||SdS)NZ relay_addresszconfiguring MTA for %sz$No relay; skipping MTA configurationr,zAHKLM\SOFTWARE\Wow6432Node\Mail Enable\Mail Enable\Connectors\SMTPregaddz/vzForward All Outbound Hostz/dz/fzset mail relayrFzForward All Outbound Enabled1zenable mail relay)r-rMdebugr0r) rr"rrbZrelayZ smtp_registryr7r1r2rrrrC@s<      zWindows.do_configure_mtac CsTd}tdd|g|d\}}}|dkrFtd||d||||fS||||S)N configure_ipszsetNetwork.ps1zconfigure networkr:rz failed to configure ips: %s (%s)F)rrMerrorr0) rZ vm_address addressesZgatewayr#rr7r1r2rrrrgVs zWindows.configure_ipscCs:d}d|dd}t|d\}}}|dk||||fS)Nchange_hostnamez [string]$hostname='hostnameaU'; if($hostname.tolower().StartsWith('www.')){ $hostname=$hostname.substring(4) } if($hostname.IndexOf('.') -gt -1){ $hostSet=$false; $hostname.Split('.') | %{ if($_.Length -gt 0 -and $hostSet -eq $false){ $hostname=$_; $hostSet=$true; } } } Rename-Computer -NewName $hostname -Force; ZchangeHostnamerrT)rr"r#rr@r7r1r2rrrrj`szWindows.change_hostnamecCs.tdd\}}}t|}t|d}d|iS)Nzcpu_utilization.ps1z#get_utilization|get_cpu_utilizationZcpuTimePercentZcpuUsed)rjsonloadsfloat)rr=r1 utilizationcpurrr_get_cpu_utilizationus   zWindows._get_cpu_utilizationcCsDtdd\}}}t|}t|d}t|d}||}||dS)Nzmemory_utilization.ps1z&get_utilization|get_memory_utilizationZ ramTotalMiBZ ramFreeMiB)Z memoryTotalZ memoryUsed)rrlrmint)rr=r1roZ memory_totalZ memory_freeZ memory_usedrrr_get_memory_utilizations    zWindows._get_memory_utilizationcOs|d}|d}td||djf|}|d}|rH|dj|d7}|d}|rf|d j|d 7}|d } | r|d j| d 7}|dd} | r|d7}dj||d} t| d\} } }| dk|| |dfS)N customer_key template_idszBInstalling Fortimonitor agent - Customer Key: %s, Template IDs: %sz9templates = {template_ids} enable_countermeasures = false server_keyz server_key = {server_key})rvfqdnz fqdn = {fqdn})rw serverNamez server_name = {serverName})rxdisable_server_matchFz disable_server_match = truea$Manifest = @" {manifest_file_contents} "@ $Manifest | Out-File -FilePath "C:\FortimonitorAgent.manifest" mkdir c:\fortimonitor_temp cd c:\fortimonitor_temp Invoke-WebRequest https://repo.fortimonitor.com/install/win/fortimonitor_agent_windows.ps1 -OutFile c:\fortimonitor_temp\fortimonitor_agent_windows.ps1 # noqa E501 c:\fortimonitor_temp\fortimonitor_agent_windows.ps1 -customer_key {customer_key})manifest_file_contentsrtzInstall Panoptarinstall_panopta)rMrNr`r-rr0)rr"rkwargsrtrurzrvrwZ server_nameryr@r7r1r2rrrr{s6     zWindows.install_panopta) agent_namercCsPdj|d}t|dj|d\}}}|rL|dkr6d}nd}t|dj|dSdS) z9 delete agent if present from the server z&Get-Package -Name '{agent_name} Agent')r}zCheck {agent_name} InstalledPanoptaz$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -match "Panopta Agent" } $app.Uninstall();z$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -match "FortiMonitor Agent" } $app.Uninstall();zDelete {agent_name}rzPanopta agent is not installedr,)r`r)rr}rRr=r1r@rrr _delete_agents zWindows._delete_agent) manifest_namercCsDtjdj|dr2dj|d}t|dj|dSddj|ddfS)z, delete agent manifest file z C:\{manifest_name}Agent.manifest)ru=Remove-Item –path C:\{manifest_name}Agent.manifest –forcez$Delete {manifest_name} manifest filerz,{manifest_name} manifest file is not presentr,ospathexistsr`r)rrr@rrr_delete_agent_manifests zWindows._delete_agent_manifest)temp_dirrcCsDtjdj|dr2dj|d}t|dj|dSddj|ddfS)z4 delete agent's temporary directory z C:\{temp_dir})rz/Remove-Item -path C:\{temp_dir} -Recurse -forcezDelete {temp_dir} directoryrz#{temp_dir} directory is not presentr,r)rrr@rrr_delete_temp_directorys zWindows._delete_temp_directoryc Os|d\}}}|d\}}}||} |d|}|d|}| dkrdtdd|||dfSz|d\}} } |d\}}}| d|} | d|} |d| }|d| }|d \} } } |d \} }}||| | } | d|} | d|} |d| }|d| }WnPttfk rh}z,d }t|d|d t |dfWYSd }~XYnX| dk|||dfS)z delete panopta and fm-agent if present from the server and removes the panopta/fm-agent manifest file :return result of uninstall operation r~Z FortiMonitor rz'failed to remove panopta/fm-agent agentFdelete_panoptaZ FortimonitorZ panopta_tempZfortimonitor_tempzJFailed to unlink Panopta/FortiMonitor manifest file or temporary directoryr,N) rrMrhr0rrr]OSErrorr^r_)rrr|Z exit_code1r1r2Z exit_code2outputrhr7Zouts2Zerrs2Z exit_code3Z exit_code4r5msgrrrrs6           ,zWindows.delete_panoptac Os`d}t|d\}}}|rRd}t|d\}}}|dkr\tdd|||dfSn d \}}}d S) zR upgrade panopta on the server :return result of upgrade operationz!Get-Package -Name 'Panopta Agent'zCheck Panopta InstalledzInvoke-WebRequest https://repo.fortimonitor.com/install/win/fm-upgrade.ps1 -OutFile fm-upgrade.ps1 # noqa E501 pylint: disable=line-too-long .\fm-upgrade.ps1 -autoupdatezUpgrade Panoptarz5failed to upgrade panopta agent to fortimonitor agentFupgrade_panoptarN)rrMrhr0) rrr|rRr7r1r=r@r2rrrrs zWindows.upgrade_panoptac Osnd}tj|sd}dj|d}t|d\}}}|dkr@tddj|d}t|d \}}}|d k|d d fS) z Look into the Agent.config xml file and find the ServerKey add component. :return: The value of the ServerKey add component in the Agent.config file. z0C:\Program Files (x86)\PanoptaAgent\Agent.configz5C:\Program Files (x86)\FortiMonitorAgent\Agent.configz*[System.IO.File]::Exists("{agent_config}")) agent_configzCheck Config FileFalsezPanopta config file not foundz[xml]$config = Get-Content "{agent_config}" $serverKey = $config.agent.service.add | ? {{$_.key -eq "ServerKey"}} | % {{echo $_.value}} echo $serverKey zGet Server KeyrF)r1Z append_info)rrrr`r ValueError) rrr|rrRr7r1r=r@rrrget_panopta_server_keys  zWindows.get_panopta_server_key)valid_resolversinvalid_resolversrc Osd|}d|}tddd|d|gd\}}} |dkrx| rx| d} | d} | d } t| d krx| d d d } |dk||| dfS)a If the server has any of the listed invalid resolvers, replace them with the valid resolvers. Otherwise, leave the resolvers as-is. :param valid_resolvers: A list of valid dns nameservers to be added to the server (if needed) :param invalid_resolvers: A list of invalid dns nameservers to be removed from the server :return: A Nydus result ,zupdate_invalid_resolvers.ps1update_invalid_resolversz-validResolversz-invalidResolvers)rrr)r+:rYz r,)joinrsplitlenstripr3r0) rrrrr|Zvalid_resolvers_argZinvalid_resolvers_argr7r1r2Z err_arrayerr_msgZ err_msg_arrayrrrrs$      z Windows.update_invalid_resolvers)r=rcGs |ddS)zConfigure the system so it can be accessed remotely with winexe. We use winexe in our integration tests to inspect VMs. :param *_: op chaining arguments :returns: operation result zenable_winexe.ps1 enable_winexer;)rr=rrrr=szWindows.enable_winexe)portr=rcGst|}|jdd|dgdS)zConfigure the system so the specified port can be accessed remotely. :param port: The port number to open :param *_: op chaining arguments :returns: operation result configure_port.ps1 open_portopenr:rrr!rrr=rrrrFszWindows.open_portcGst|}|jdd|dgdS)zConfigure the system so the specified port cannot be accessed remotely. :param port: The port number to close :param *_: op chaining arguments :returns: operation result r close_portcloser:rrrrrrPszWindows.close_port)pypi_urlcGs*|d|jd|j}|jdd|gdS)z Install qemu guest agent on the vm :param pypi_url: The url for the pypi server where the package is located /-//zinstall_qemu_agent.ps1install_qemu_agentr:)DISTROQEMU_PACKAGE_NAMEr!rrrZ package_urlrrrrZszWindows.install_qemu_agentcGs*|d|jd|j}|jdd|gdS)z| Install devcon on the vm :param pypi_url: The url for the pypi server where the package is located rrzinstall_devcon.ps1install_devconr:)rDEVCONr!rrrrrcszWindows.install_devcon)rrc Gs~d}d}d}t||\}}}|dkrl|rl|dD]4}|dr2d} t| |} |dj| dd 7}q2|Sd ||||fS) z9Returns the Operating System information using systeminfo get_os_infoz NAME=windows z! systeminfo | Select-String "OS" rrzOS Name:z \b\d{4}\bzVERSION={version} )versionF)rr startswithresearchr`r8r0) rrrZos_infor@r7r1r2linepatternrrrrrls   zWindows.get_os_info)pathsrcCs@ttj|d}ttttfddd}tt||}d|iS)a Op for retrieving file info (if file exists), for now for Windows returning only info whether file exists :param paths: Comma-separated list of paths of files :return: Dict with filenames and files related info (if file present) r)rrcSsttj|}||dS)N)fileNamer)rrrnormpathr)rZfile_locrrr_get_file_dictsz-Windows.get_file_info.._get_file_dictfiles)mapr_rrrrlist)rrZ paths_listrZout_listrrr get_file_infoszWindows.get_file_info)N)N)N)N)N)N)N)N)N)N)6__name__ __module__ __qualname__ZDISK_UTILIZATION_PATHr ZOPERATING_SYSTEMZop_typerrrrrr_rrrr!rr$r9boolr>r?rArBrHrOrSrUrWr[rCrgrjrqrsr{rrrrrrrrrrrrrrrrrrrrrrrsR      (  P   #  "  "     rc@s eZdZdS) Windows2016Nrrrrrrrrsrc@s eZdZdS) Windows2019Nrrrrrrsrc@s eZdZdS) Windows2022Nrrrrrrsr)#pathlibrtypingrrrrrrlloggingrrZcustomer_local_opsrr r Zcustomer_local_ops.exceptionsr Z#customer_local_ops.operating_systemr Zcustomer_local_ops.utilr rZcustomer_local_ops.util.executerr getLoggerrrMr_rrrrrrrrrs*