def send_files(self, incremental_patches_list): """ Send config and patch files :return: void """ f_action = FileAction(self.config_fullpath) temporary_config = tempfile.NamedTemporaryFile(delete=False) # check the configuration file # TODO: make it more compact if re.findall("[.]gz\Z", self.config_fullpath): log.notice('gz extension') # uncompress the gzip config file # return configuration temporary folder temporary_config = f_action.decompress_gz(temporary_config) else: # read already uncompressed configuration with open(self.config_fullpath, 'rb') as in_file: config = in_file.read() # Store uncompressed temporary file temporary_config.write(config) # Get kernel version from the configuration file header # self.kernel_version = f_action.config_kernel_version(temporary_config) self.rest_manager.set_kernel_version(self.kernel_version) log.notice('debug: kernel version = ' + self.rest_manager.get_kernel_version()) send_api = '/elivepatch/api/v1.0/get_files' # send uncompressed config and patch files fullpath self.rest_manager.send_files(temporary_config, self.main_patch_fullpath, incremental_patches_list, send_api)
def version(self): """ Function for as the server version and print on screen """ url = self.server_url + '/elivepatch/api/v1.0/agent' r = requests.get(url) log.notice(r.json())
def load(self, patch_fulldir, livepatch_fulldir): """ kpatch load live patch into the client machine working kernel :param patch_fulldir: String patch full directory :param livepatch_fulldir: String live patch full directory """ try: _command(['sudo', 'kpatch', 'load', livepatch_fulldir]) log.notice('patch_fulldir:' + str(patch_fulldir) + ' livepatch_fulldir: '+ str(livepatch_fulldir)) self._save(patch_fulldir, livepatch_fulldir) except: log.notice('failed to load the livepatch')
def __init__(self, restserver_url, kernel_version, session_uuid=None): self.config_fullpath = '' self.main_patch_fullpath = '' self.restserver_url = restserver_url self.kernel_version = kernel_version if session_uuid: self.session_uuid = session_uuid else: self.session_uuid = id_generate_uuid() log.notice('This session uuid: ' + str(self.session_uuid)) self.rest_manager = restful.ManaGer(self.restserver_url, self.kernel_version, self.session_uuid)
def decompress_gz(self, temporary): """ Uncompress gzipped configuration :return: Uncompressed configuration file path """ path_gz_file = self.full_path log.notice('path_gz_file: ' + path_gz_file + ' temporary_path_uncompressed_file: ' + temporary.name) if os.path.isfile(path_gz_file): with gzip.open(path_gz_file, 'rb') as in_file: uncompressed_output = in_file.read() # Store uncompressed file temporary.write(uncompressed_output) return temporary
def get_livepatch(self, patch_folder): """ Save the patch in the incremental patches folder and install the livepatch :param patch_folder: Main patch that will be saved in the incremental patches folder. """ patch_manager = patch.ManaGer() url = self.server_url + '/elivepatch/api/v1.0/send_livepatch' payload = {'KernelVersion': self.kernel_version, 'UUID': self.uuid} try: r = requests.get(url, json=payload) if r.status_code == requests.codes.ok: # livepatch returned ok try: b = BytesIO(r.content) with open('myfile.ko', 'wb') as out: out.write(r.content) r.close() log.notice(b) except: log.notice('livepatch not found') r.close() except: self._catching_exceptions_exit(self.get_livepatch) with tempfile.TemporaryDirectory() as elivepatch_uuid_dir: livepatch_fulldir = os.path.join(elivepatch_uuid_dir, 'livepatch.ko') if os.path.exists('myfile.ko'): if not os.path.exists(elivepatch_uuid_dir): os.makedirs(elivepatch_uuid_dir) shutil.copy("myfile.ko", livepatch_fulldir) log.notice('livepatch saved in ' + elivepatch_uuid_dir + '/ folder') patch_manager.load(patch_folder, livepatch_fulldir) else: log.notice('livepatch not received')
def list(self, kernel_version): """ Listing incremental patches :param kernel_version: String with kernel version :return: list of incremental patch """ kernel_sources = 'gentoo-sources' patch_filename = [] previous_patches = [] # search eapply_user patches # local basedir=${PORTAGE_CONFIGROOT%/}/etc/portage/patches try: portage_configroot = os.environ['PORTAGE_CONFIGROOT'] except: portage_configroot = os.path.join('/etc', 'portage', 'patches') kernel_patch_basedir_PN = os.path.join(portage_configroot, 'sys-kernel', kernel_sources) kernel_patch_basedir_P = os.path.join(portage_configroot, 'sys-kernel', kernel_sources + '-' + kernel_version) basedir = [kernel_patch_basedir_PN, kernel_patch_basedir_P] for path in basedir: for (dirpath, dirnames, filenames) in os.walk(path): if filenames and not dirnames: for filename in filenames: if filename.endswith('.patch'): log.notice('dirpath: '+str(dirpath),'filename: '+str(filename)) incremental_patch_fullpath = os.path.join(dirpath, filename) log.notice(incremental_patch_fullpath) patch_filename.append(incremental_patch_fullpath) # os.walk() walks in random order, perform a lexical sort patch_filename.sort() # search previous livepatch patch folder for (dirpath, dirnames, filenames) in os.walk(self.tmp_patch_folder): if filenames and not dirnames: for filename in filenames: if filename.endswith('.patch'): log.notice(str('dirpath: '+str(dirpath) + 'filename: '+str(filename))) incremental_patch_fullpath = os.path.join(dirpath, filename) log.notice(incremental_patch_fullpath) previous_patches.append(incremental_patch_fullpath) # os.walk() walks in random order, perform a lexical sort previous_patches = sorted(previous_patches, key=lambda elive: int(elive.replace('/elivepatch.patch','').split('_')[1])) # Append the previous patches to the eapply_user patches list patch_filename.extend(previous_patches) log.notice('List of current patches:') return patch_filename
def cve_git_id(self, kernel_version): major_version, minor_version, revision_version = kernel_version.split( '.') print(major_version, minor_version, revision_version) security_file = open( self.repo_dir + str(major_version) + "." + str(minor_version) + "/" + str(major_version) + "." + str(minor_version) + "_security.txt", "r") security_versions = [] for line in security_file: if "CVEs fixed in" in line: security_versions_tmp = line.strip().split(' ')[3][:-1] # if there is not revision, set revision as 0 sv_split_tmp = security_versions_tmp.split('.') if len(sv_split_tmp) == 2: security_versions.append(0) else: security_versions.append( security_versions_tmp.split('.')[2]) security_file.close() log.notice('[debug] security versions: ' + str(security_versions)) cve_2d_list = [] for version in security_versions: if int(version) > int(revision_version): cve_2d_list.append( self.cve_id(major_version, minor_version, version)) cve_outfile_list = [] patch_index = 0 if not os.path.exists(self.cve_patches_dir): os.mkdir(self.cve_patches_dir) for cve_list in cve_2d_list: # Remove duplicated cve_id from the cve list for not add the same patch cve_list = [ ii for n, ii in enumerate(cve_list) if ii not in cve_list[:n] ] for cve_id in cve_list: cve_outfile = self.download_cve_patch( cve_id, str(patch_index).zfill(5)) cve_outfile_list.append([cve_outfile[0], cve_outfile[1].name]) patch_index += 1 return cve_outfile_list
def send_files(self, temporary_config, new_patch_fullpath, incremental_patches, api): """ Function for send files and build live patch (server side) :param temporary_config: configuration file full path :param new_patch_fullpath: main patch full path :param incremental_patches: List with incremental patches paths :param api: RESTFul server path :return: json with response """ url = self.server_url + api # we are sending the file and the UUID # The server is dividing user by UUID # UUID is generated with python UUID # TODO: add the UUID in the json location instead of headers response_dict = None headers = {'KernelVersion': self.kernel_version, 'UUID': self.uuid} # Static patch and config filename files = [] counter = 0 log.notice('incremental_patches: ' + str(incremental_patches)) for incremental_patch_fullpath in incremental_patches: if incremental_patch_fullpath.endswith('.patch'): # TODO: we need to close what we open read_incremental_patch = open(incremental_patch_fullpath, 'rb') files.append( ('patch', (str(counter).zfill(5) + '.patch', read_incremental_patch, 'multipart/form-data', { 'Expires': '0' }))) counter += 1 files.append( ('main_patch', ('main.patch', open(new_patch_fullpath, 'rb'), 'multipart/form-data', { 'Expires': '0' }))) files.append(('config', ('config', open(temporary_config.name, 'rb'), 'multipart/form-data', { 'Expires': '0' }))) log.notice(str(files)) try: response = requests.post(url, files=files, headers=headers) log.notice('send file: ' + str(response.json())) response_dict = response.json() except requests.exceptions.ConnectionError as e: log.notice('connection error: %s' % e) temporary_config.close() except: self._catching_exceptions_exit(self.send_files) temporary_config.close() return response_dict
def _command(bashCommand, kernel_source_dir=None, env=None): """ Popen override function :param bashCommand: List of command arguments to execute :param kernel_source_dir: the source directory of the kernel :return: void """ # Inherit the parent environment and update the private copy if env: process_env = os.environ.copy() process_env.update(env) env = process_env if kernel_source_dir: log.notice(bashCommand) process = subprocess.Popen(bashCommand, stdout=subprocess.PIPE, cwd=kernel_source_dir, env=env) output, error = process.communicate() log.notice(output) else: log.notice(bashCommand) process = subprocess.Popen(bashCommand, stdout=subprocess.PIPE, env=env) output, error = process.communicate() log.notice(output)
def dispatch(self, config): log.debug(str(config)) if config.cve: patch_manager = patch.ManaGer() applied_patches_list = patch_manager.list(config.kernel_version) log.notice(applied_patches_list) cve_repository = security.CVE() if not os.path.isdir("/tmp/kernel_cve"): log.notice("Downloading the CVE repository...") cve_repository.git_download() else: log.notice("CVE repository already present.") log.notice("updating...") cve_repository.git_update() if config.clear: if os.path.isfile('cve_ids'): os.remove('cve_ids') cve_patch_list = cve_repository.cve_git_id(config.kernel_version) new_cve_patch_list = cve_patch_list cve_previous_patch_list = [] # checking if we have a previous cve_ids list if os.path.isfile('cve_ids'): cve_db = shelve.open('cve_ids') for i in (list(cve_db.keys())): cve_previous_patch_list.append([i, cve_db[i]]) cve_db.close() new_cve_patch_list = [] # checking if there is any new cve patch in the repository for cve_patch_id in cve_patch_list: if cve_patch_id not in cve_previous_patch_list: new_cve_patch_list.append(cve_patch_id) # converting new cve to live patch for cve_id, cve_patch in new_cve_patch_list: with shelve.open('cve_ids') as cve_db: cve_db[cve_id] = cve_patch log.notice('merging cve patches...') with tempfile.NamedTemporaryFile(dir='/tmp/', delete=False) as portage_tmpdir: log.notice('portage_tmpdir: '+portage_tmpdir.name) for cve_id, cve_file in cve_patch_list: with open(cve_file,'rb+') as infile: portage_tmpdir.write(infile.read()) livepatch(config.url, config.kernel_version, config.config, portage_tmpdir.name, applied_patches_list) log.notice(new_cve_patch_list) elif config.patch: patch_manager = patch.ManaGer() applied_patches_list = patch_manager.list(config.kernel_version) log.notice(str(applied_patches_list)) livepatch(config.url, config.kernel_version, config.config, config.patch, applied_patches_list) elif config.version: log.notice('elivepatch version: '+str(VERSION)) else: print('--help for help\n\ you need at list --patch or --cve')