def _run_process(self, filename, command_name, command_args, flush_callback=None, message=None): ext = os.path.splitext(filename)[1] if ext in (".py", ".pyw", ".pyc"): command = self._python_runner # -u: sets unbuffered output args = [command, "-u", "-W ignore::DeprecationWarning", filename, command_name] else: command = filename args = [command, command_name] # Set timeout from command cmd_timeout = int(command_args.get("timeout", self.timeout)) if command_name: log.info("Running %s from %s (timeout: %i)" % (command_name, filename, cmd_timeout)) else: log.info("[INIT] Loading commands from %s" % filename) mem_clean("spawnProcess [start]") crp = CommandRunnerProcess(cmd_timeout, command_args, flush_callback, message) d = crp.getDeferredResult() reactor.spawnProcess(crp, command, args, env=self.env) del cmd_timeout, filename, command_name, command_args del flush_callback, message, args mem_clean("spawnProcess [end]") return d
def _run_process(self, filename, command_name, command_args, flush_callback=None, message=None): ext = os.path.splitext(filename)[1] if ext in ('.py', '.pyw', '.pyc'): command = self._python_runner # -u: sets unbuffered output args = [command, '-u', '-W ignore::DeprecationWarning', filename, command_name] else: command = filename args = [command, command_name] # Set timeout from command cmd_timeout = self.timeout if 'timeout' in command_args: cmd_timeout = int(command_args['timeout']) if command_name: log.info("Running %s from %s (timeout: %i)" % (command_name, filename, cmd_timeout)) else: log.info("[INIT] Loading commands from %s" % filename) crp = CommandRunnerProcess(cmd_timeout, command_args, flush_callback, message) d = crp.getDeferredResult() reactor.spawnProcess(crp, command, args, env=self.env) return d
def _processCommand(self, message): log.debug('Process Command') if self.public_key: if not self._verify_message(message): log.critical('[RSA CHECK: Failed] Command from %s has bad signature (Ignored)' % message.from_) result = (_E_UNVERIFIED_COMMAND, '', 'Bad signature', 0) self._onCallFinished(result, message) return else: # No public key, RSA functions are not available on this system log.warn('[RSA CHECK: No available] WARNING: Running unverified Command from %s' % message.from_) flush_callback = self._Flush message.command_replaced = message.command.replace('.', '_') d = self.command_runner.run_command(message.command_replaced, message.command_args, flush_callback, message) if d: d.addCallbacks(self._onCallFinished, self._onCallFailed, callbackKeywords={'message': message}, errbackKeywords={'message': message}, ) return d else: log.info("Command Ignored: Unknown command: %s" % message.command) result = (_E_RUNNING_COMMAND, '', "Unknown command: %s" % message.command, 0) self._onCallFinished(result, message) return
def _rsa_verify(self, text, signature, command, sender): def _emsa_pkcs1_v1_5_encode(M, emLen): # for PKCS1_V1_5 signing: SHA1DER = '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14' SHA1DERLEN = len(SHA1DER) + 0x14 H = SHA.new(M).digest() T = SHA1DER + H if emLen < (SHA1DERLEN + 11): log.error('[RSA CHECK: Error] intended encoded message length too short (%s)' % emLen) return ps = '\xff' * (emLen - SHA1DERLEN - 3) if len(ps) < 8: log.error('[RSA CHECK: Error] ps length too short') return return '\x00\x01' + ps + '\x00' + T signature = base64.b64decode(signature) em = _emsa_pkcs1_v1_5_encode(text, len(signature)) if em: signature = number.bytes_to_long(signature) if self.public_key.verify(em, (signature,)): log.info("[RSA CHECK: OK] command: %s - from: %s" % (command, sender)) return True log.error("[RSA CHECK: Error] %s - from: %s" % (command, sender)) return False
def _processCommand(self, message): if not self.verify.signature(message): result = (_E_UNVERIFIED_COMMAND, '', 'Bad signature', 0) self._onCallFinished(result, message) return flush_callback = self._flush message.command_name = message.command.replace('.', '_') mem_clean('run_command [start]') d = self.command_runner.run_command(message, flush_callback) # Clean message message.command_args = {} if d: d.addCallbacks(self._onCallFinished, self._onCallFailed, callbackKeywords={'message': message}, errbackKeywords={'message': message}, ) del message mem_clean('run_command [end]') return d else: log.info("Command Ignored: Unknown command: %s" % message.command) result = (_E_RUNNING_COMMAND, '', "Unknown command: %s" % message.command, 0) self._onCallFinished(result, message) del message mem_clean('run_command [end]') return
def _run_process(self, filename, command_name, command_args, flush_callback=None, message=None): need_sudo = ['plugin_pip.py', 'plugin_service.py', 'plugin_update.py', 'plugin_haproxy.py', 'plugin_monitor.py', 'plugin_pip_extra.py', 'plugin_puppet.py', 'plugin_saltstack.py', 'plugin_proc.py'] ext = os.path.splitext(filename)[1] if ext == '.py': from sys import platform if platform.startswith("win32") or os.path.split(filename)[1] not in need_sudo: command = self._python_runner args = [command, '-u', '-W ignore::DeprecationWarning', filename, command_name] else: command = 'sudo' # -u: sets unbuffered output args = [command, self._python_runner, '-u', '-W ignore::DeprecationWarning', filename, command_name] else: command = filename args = [command, command_name] # :TODO Set timeout from command cmd_timeout = int(command_args.get('timeout',self.timeout)) if command_name: log.info("Running %s from %s (timeout: %i)" % (command_name, filename, cmd_timeout)) else: log.info("[INIT] Loading commands from %s" % filename) crp = CommandRunnerProcess(cmd_timeout, command_args, flush_callback, message) d = crp.getDeferredResult() reactor.spawnProcess(crp, command, args, env=self.env) del cmd_timeout, filename, command_name, command_args del flush_callback, message, args return d
def _check_memory(self, running_commands): rss, vms = mem_usage() log.info("Running commands: %i" % running_commands) log.info("Current Memory usage: rss=%sMB | vms=%sMB" % (rss, vms)) if not running_commands and rss > _CHECK_RAM_MAX_RSS_MB: log.critical("Max allowed RSS memory exceeded: %s MB, exiting." % _CHECK_RAM_MAX_RSS_MB) reactor.stop()
def _onCallFailed(self, failure, *argv, **kwargs): log.error("onCallFailed") log.info(failure) if 'message' in kwargs: message = kwargs['message'] result = (2, '', failure, 0) del self.running_commands[message.command_name] self.num_running_commands -= 1 self._onCallFinished(result, message)
def mem_usage(): rss, vms = 0, 0 try: rss, vms = psutil.Process(getpid()).get_memory_info() rss /= 1000000.0 vms /= 1000000.0 except: pass log.info("Current Memory usage: rss=%sMB | vms=%sMB" % (rss, vms)) return rss, vms
def mem_clean(where="", dolog=False): _collect = collect() rss, vms = mem_usage() string = "_mem_clean: %s collected %d objects. (current mem: rss=%sMB | vms=%sMB)" % (where, _collect, rss, vms) if dolog: log.info(string) else: log.debug(string) del _collect, where, dolog, rss, vms, string
def __init__(self, config): """ XMPP agent class. """ log.info("Starting Agent...") log.info("Loading Commands...") self.command_runner = CommandRunner(config['Plugins']) log.info("Setting up Certificate") self.verify = ECVerify() log.info("Setting up Memory checker") self.running_commands = {} self.num_running_commands = 0 self.timestamp = 0 self.memory_checker = LoopingCall(self._check_memory, self.running_commands) self.memory_checker.start(_CHECK_RAM_INTERVAL) self.keepalive = LoopingCall(self._reconnect) log.debug("Loading XMPP...") Client.__init__( self, config['XMPP'], [("/iq[@type='set']", self.__onIq), ], resource='ecm_agent-%d' % AGENT_VERSION_PROTOCOL)
def check_config(self): uuid = self._get_stored_uuid() account_id = self.get_stored_account() if not uuid and not account_id: # Is not an update and no account is set log.error('Please configure agent with ./configure --account=XXXXX') raise Exception('Please configure agent with ./configure --account=XXXXX') unique_id = self._get_unique_id() if not unique_id: log.error('Could not obtain UNIQUE_ID. Please set up XMPP manually') raise Exception('Could not obtain UNIQUE_ID. Please set up XMPP manually') # Check all data valid for v3 if uuid and not '@' in uuid and account_id and self.is_unique_id_same(unique_id): log.debug('UNIQUE ID has not changed. Skip UUID check') else: # Try to get uuid (one hour and a half loop: 360x15) json_data = None for i in range(360): log.info("Trying to get UUID via URL (meta-data v2)") json_data = yield self._get_config(unique_id) if json_data: break sleep(15) # Decode metadata meta_data = self.parse_meta_data(json_data) if not meta_data: log.error('Could not obtain UUID. Please set up XMPP manually') raise Exception('Could not obtain UUID. Please set up XMPP manually') if not self['XMPP'].get('password'): self['XMPP']['password'] = hex(random.getrandbits(256))[2:-1] # Updates from v2 to v3 write account info if not account_id and meta_data.get('account'): self['XMPP']['account'] = meta_data.get('account') self['XMPP']['user'] = meta_data['uuid'] self['XMPP']['unique_id'] = unique_id self.write() returnValue(True)
def check_uuid(self): mac = self._get_mac() # Always generate a new password if not is set if not self['XMPP']['password']: self['XMPP']['password'] = hex(random.getrandbits(128))[2:-1] if mac: if str(mac) == str(self._getStoredMAC()): log.debug("MAC has not changed. Skip UUID check") else: # Try to get uuid uuid = None for i in range(30): try: uuid = yield self._getUUID() if uuid: break except: pass sleep(20) if not uuid: log.error("ERROR: Could not obtain UUID. please set up XMPP manually in %s" % self.filename) returnValue(False) if str(uuid) == str(self._getStoredUUID()): log.debug("UUID has not changed.") # Update mac self['XMPP']['mac'] = mac self.write() else: log.info("UUID has changed, reconfiguring XMPP user/pass") self['XMPP']['user'] = '******'.join((uuid, self['XMPP']['host'])) self['XMPP']['mac'] = mac self.write() returnValue(True) else: log.error("ERROR: Could not obtain MAC. please set up XMPP manually in %s" % self.filename) returnValue(False)
def _get_unique_id(): """ Try to get a unique identified, Some providers may change UNIQUE_ID on stop/start Use a low timeout to speed up agent start when no meta-data url """ log.info("Trying to get UNIQUE ID") unique_id = None try: # Get info from meta-data socket.setdefaulttimeout(URL_METADATA_TIMEOUT) for metadata_type in URL_METADATA_INSTANCE_ID.keys(): try: request = urllib2.Request(URL_METADATA_INSTANCE_ID[metadata_type]) # Google needs header if metadata_type == 'gce': request.add_header('Metadata-Flavor', 'Google') response = urllib2.urlopen(request) instance_id = response.readlines()[0] response.close() except urllib2.URLError: continue if instance_id: unique_id = metadata_type + '::' + instance_id break except: pass finally: # Set default timeout again socket.setdefaulttimeout(10) if not unique_id: # Use network mac address from uuid import getnode from re import findall unique_id = 'mac::' + ':'.join(findall('..', '%012x' % getnode())) return unique_id
def __init__(self, key_file=None): self.public_key = None if not key_file: key_file = CERTIFICATE_FILE try: if Crypto.version_info[:2] >= (2, 2): _public_key = self._read_pub_key(key_file) if _public_key: key = PublicKey.RSA.importKey(_public_key) self.public_key = key.publickey() except: pass if not self.public_key: log.info('PyCrypto not available or version is < 2.2: Please upgrade: http://www.pycrypto.org/')
def check_uuid(self): unique_id = self._get_unique_id() if unique_id: if str(unique_id) == str(self._get_stored_unique_id()): log.debug("UNIQUE ID has not changed. Skip UUID check") else: # Try to get uuid (one hour and a half loop: 360x15) uuid = None for i in range(360): try: uuid = yield self._get_uuid() if uuid: break except Exception: pass sleep(15) if not uuid: log.error("ERROR: Could not obtain UUID. please set up XMPP manually in %s" % self.filename) raise Exception("Could not obtain UUID") if str(uuid) == str(self._get_stored_uuid()): log.debug("UUID has not changed.") self["XMPP"]["unique_id"] = unique_id self.write() else: log.info("UUID has changed, reconfiguring XMPP user/pass") self["XMPP"]["user"] = "******".join((uuid, self["XMPP"]["host"])) self["XMPP"]["unique_id"] = unique_id self.write() returnValue(True) else: log.error("ERROR: Could not obtain UNIQUE_ID. please set up XMPP manually in %s" % self.filename) raise Exception("Could not obtain UUID")
def __init__(self, config): """ XMPP agent class. """ log.info("Starting agent...") self.config = config log.info("Setting up certificate") self.public_key = None try: if Crypto.version_info[:2] >= (2, 2): _public_key = self._read_pub_key() if _public_key: key = PublicKey.RSA.importKey(_public_key) self.public_key = key.publickey() except: pass if not self.public_key: log.warn('PyCrypto not available or version is < 2.2: Please upgrade: http://www.pycrypto.org/') log.info("Loading commands...") self.command_runner = CommandRunner(config['Plugins']) log.debug("Loading XMPP...") observers = [ ('/iq', self.__onIq), ] Client.__init__(self, self.config['XMPP'], observers, resource='ecm_agent-%d' % AGENT_VERSION_PROTOCOL )
def _get_uuid(self): if self["XMPP"].as_bool("manual"): log.info("Skipping UUID auto configuration as manual flag is set.") return self["XMPP"]["user"].split("@")[0] else: # Try to get from preconfigured log.info("try to get UUID via preconfiguration") uuid = self._get_uuid_pre_configured() if not uuid: # Try to configure via URL (ECM meta-data) log.info("try to get UUID via URL (ecagent meta-data)") uuid = self._get_uuid_via_web() return uuid
def _getUUID(self): if self['XMPP'].as_bool('manual'): log.info("Skipping UUID auto configuration as manual flag is set.") return self['XMPP']['user'].split('@')[0] else: # Try to get from preconfigured log.info("try to get UUID via preconfiguration") uuid = self._getUUIDPreConfig() if not uuid: # Try to configure via URL (ECM meta-data) log.info("try to get UUID via URL (ecagent meta-data)") uuid = self._getUUIDViaWeb() return uuid
def _reconnect(self): """ Disconnect the current reactor to try to connect again """ log.info("No data received in %ss: Trying to reconnect" % KEEPALIVED_TIMEOUT) reactor.disconnectAll()