def get_soft_from_docker_image(docker_driver, image_name): # Start container try: # Try to start the docker container with the next entrypoint: 'sleep 30' container_id = docker_driver.create_container(image_name, entrypoint='sleep 30') docker_driver.docker_start(container_id) except docker.errors.ImageNotFound: raise DagdaError('No such image: ' + image_name + ':latest') except docker.errors.NotFound: docker_driver.docker_remove_container(container_id) # 'sleep' is not in the $PATH, so try to start the docker container with its default entrypoint try: container_id = docker_driver.create_container(image_name) docker_driver.docker_start(container_id) except: docker_driver.docker_remove_container(container_id) raise DagdaError('The docker container with the <' + image_name + '> image name can not be started.') # Get all installed packages try: products = get_soft_from_docker_container_id(docker_driver, container_id) except DagdaError: # Stop container docker_driver.docker_stop(container_id) # Clean up docker_driver.docker_remove_container(container_id) # Re-raise exception raise # Stop container docker_driver.docker_stop(container_id) # Clean up docker_driver.docker_remove_container(container_id) # Return packages return products
def pre_check(self): # Init linux_distro = platform.linux_distribution()[0] uname_r = os.uname().release # Check requirements if not os.path.isfile('/.dockerenv'): # I'm living in real world! if 'Red Hat' == linux_distro or 'CentOS' == linux_distro or 'Fedora' == linux_distro \ or 'openSUSE' == linux_distro: # Red Hat/CentOS/Fedora/openSUSE return_code = subprocess.call( ["rpm", "-q", "kernel-devel-" + uname_r], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) elif 'Debian' == linux_distro or 'Ubuntu' == linux_distro: # Debian/Ubuntu return_code = subprocess.call( ["dpkg", "-l", "linux-headers-" + uname_r], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) else: raise DagdaError('Linux distribution not supported yet.') if return_code != 0: raise DagdaError( 'The kernel headers are not installed in the host operating system.' ) else: # I'm running inside a docker container DagdaLogger.get_logger().warning( "I'm running inside a docker container, so I can't check if the kernel " "headers are installed in the host operating system. Please, review it!!" ) # Docker pull for ensuring the sysdig/falco image self.docker_driver.docker_pull('sysdig/falco') # Stops sysdig/falco containers if there are any container_ids = self.docker_driver.get_docker_container_ids_by_image_name( 'sysdig/falco') if len(container_ids) > 0: for container_id in container_ids: self.docker_driver.docker_stop(container_id) # Cleans mongodb falco_events collection self.mongodb_driver.delete_falco_events_collection() # Starts sysdig running container without custom entrypoint for avoiding: # --> Runtime error: error opening device /host/dev/sysdig0 self.running_container_id = self._start_container() time.sleep(30) logs = self.docker_driver.docker_logs(self.running_container_id, True, True, False) if "Runtime error: error opening device /host/dev/sysdig0" not in logs: self.docker_driver.docker_stop(self.running_container_id) else: raise DagdaError('Runtime error opening device /host/dev/sysdig0.')
def run(self): if not InternalServer.is_external_falco(): self.running_container_id = self._start_container( 'falco -pc -o json_output=true -o file_output.enabled=true ' + '-o file_output.filename=/host' + SysdigFalcoMonitor._falco_output_filename + self.falco_rules) # Wait 3 seconds for sysdig/falco start up and creates the output file time.sleep(3) # Check output file and running docker container if not os.path.isfile(SysdigFalcoMonitor._falco_output_filename) or \ (not InternalServer.is_external_falco() and \ len(self.docker_driver.get_docker_container_ids_by_image_name('falcosecurity/falco:0.18.0')) == 0): raise DagdaError('Falcosecurity/falco output file not found.') # Review sysdig/falco logs after rules parser if not InternalServer.is_external_falco(): sysdig_falco_logs = self.docker_driver.docker_logs( self.running_container_id, True, True, False) if "Rule " in sysdig_falco_logs: SysdigFalcoMonitor._parse_log_and_show_dagda_warnings( sysdig_falco_logs) # Read file with open(SysdigFalcoMonitor._falco_output_filename, 'rb') as f: last_file_position = 0 fbuf = io.BufferedReader(f) while True: fbuf.seek(last_file_position) content = fbuf.readlines() sysdig_falco_events = [] for line in content: line = line.decode('utf-8').replace("\n", "") json_data = json.loads(line) container_id = json_data['output_fields']['container.id'] if container_id != 'host': try: json_data['container_id'] = container_id json_data['image_name'] = json_data[ 'output_fields']['container.image.repository'] if 'container.image.tag' in json_data[ 'output_fields']: json_data['image_name'] += ":" + json_data[ 'output_fields']['container.image.tag'] sysdig_falco_events.append(json_data) except IndexError: # The /tmp/falco_output.json file had information about ancient events, so nothing to do pass except KeyError: # The /tmp/falco_output.json file had information about ancient events, so nothing to do pass last_file_position = fbuf.tell() if len(sysdig_falco_events) > 0: self.mongodb_driver.bulk_insert_sysdig_falco_events( sysdig_falco_events) time.sleep(2)
def check_docker_by_image_name(image_name): # -- Check input if not image_name: return json.dumps({ 'err': 400, 'msg': 'Bad image name' }, sort_keys=True), 400 # -- Docker pull from remote registry if it is necessary try: pulled = False if not InternalServer.get_docker_driver().is_docker_image(image_name): if ':' in image_name: tmp = image_name.split(':')[0] tag = image_name.split(':')[1] msg = 'Error: image library/' + image_name + ':' + tag + ' not found' output = InternalServer.get_docker_driver().docker_pull( tmp, tag=tag) else: msg = 'Error: image library/' + image_name + ':latest not found' output = InternalServer.get_docker_driver().docker_pull( image_name) if 'errorDetail' in output: DagdaLogger.get_logger().error(msg) raise DagdaError(msg) pulled = True except Exception as ex: message = "Unexpected exception of type {0} occurred while pulling the docker image: {1!r}" \ .format(type(ex).__name__, ex.get_message() if type(ex).__name__ == 'DagdaError' else ex.args) DagdaLogger.get_logger().error(message) return json.dumps({ 'err': 404, 'msg': 'Image name not found' }, sort_keys=True), 404 # -- Process request data = {} data['image_name'] = image_name data['timestamp'] = datetime.datetime.now().timestamp() data['status'] = 'Analyzing' id = InternalServer.get_mongodb_driver( ).insert_docker_image_scan_result_to_history(data) InternalServer.get_dagda_edn().put({ 'msg': 'check_image', 'image_name': image_name, '_id': str(id), 'pulled': pulled }) # -- Return output = {} output['id'] = str(id) output['msg'] = 'Accepted the analysis of <' + image_name + '>' return json.dumps(output, sort_keys=True), 202
def get_soft_from_docker_container_id(docker_driver, container_id): # Extract Linux image distribution response = get_os_name(docker_driver.docker_exec(container_id, 'cat /etc/os-release', True, False)) if response is None: raise DagdaError('Linux image distribution has not the "/etc/os-release" file.') else: # Get all installed packages if 'Red Hat' in response or 'CentOS' in response or 'Fedora' in response or 'openSUSE' in response: # Red Hat/CentOS/Fedora/openSUSE packages_info = docker_driver.docker_exec(container_id, 'rpm -aqi', True, False) products = parse_rpm_output_list(packages_info) elif 'Debian' in response or 'Ubuntu' in response: # Debian/Ubuntu packages_info = docker_driver.docker_exec(container_id, 'dpkg -l', True, False) products = parse_dpkg_output_list(packages_info) elif 'Alpine' in response: # Alpine packages_info = docker_driver.docker_exec(container_id, 'apk -v info', True, False) products = parse_apk_output_list(packages_info) else: raise DagdaError('Linux image distribution not supported yet.') # Return packages return products
def docker_logs(self, container_id, show_stdout, show_stderr, follow): try: return (self.cli.logs(container=container_id, stdout=show_stdout, stderr=show_stderr, follow=follow))\ .decode('utf-8') except docker.errors.APIError as ex: if "configured logging reader does not support reading" in str(ex): message = "Docker logging driver is not set to be 'json-file' or 'journald'" DagdaLogger.get_logger().error(message) raise DagdaError(message) else: message = "Unexpected exception of type {0} occurred: {1!r}" \ .format(type(ex).__name__, str(ex)) DagdaLogger.get_logger().error(message) raise ex
def read_4depcheck_output_file(image_name): filename = tempfile.gettempdir() + '/4depcheck/' + image_name + '.json' # Check file if not os.path.isfile(filename): raise DagdaError('4depcheck output file [' + filename + '] not found.') # Read file with open(filename, 'rb') as f: lines = f.readlines() raw_data = '' for line in lines: raw_data += line.decode('utf-8') # Return return raw_data
def read_depcheck_output_file(image_name): image_name = image_name.replace("/", '_') filename = '/tmp/depcheck/' + image_name # Check file if not os.path.isfile(filename): raise DagdaError('Depcheck output file [' + filename + '] not found.') # Read file with open(filename, 'rb') as f: lines = f.readlines() raw_data = '' for line in lines: raw_data += line.decode('utf-8') # Return return raw_data
def check_docker_by_image_name(image_name): # -- Check input if not image_name: return json.dumps({ 'err': 400, 'msg': 'Bad image name' }, sort_keys=True), 400 # -- Docker pull from remote registry if it is necessary try: pulled = False if not InternalServer.get_docker_driver().is_docker_image(image_name): output = InternalServer.get_docker_driver().docker_pull(image_name) if 'errorDetail' in output: msg = 'Error: image library/' + image_name + ':latest not found' DagdaLogger.get_logger().error(msg) raise DagdaError(msg) pulled = True except: return json.dumps({ 'err': 404, 'msg': 'Image name not found' }, sort_keys=True), 404 # -- Process request data = {} data['image_name'] = image_name data['timestamp'] = datetime.datetime.now().timestamp() data['status'] = 'Analyzing' id = InternalServer.get_mongodb_driver( ).insert_docker_image_scan_result_to_history(data) InternalServer.get_dagda_edn().put({ 'msg': 'check_image', 'image_name': image_name, '_id': str(id), 'pulled': pulled }) # -- Return output = {} output['id'] = str(id) output['msg'] = 'Accepted the analysis of <' + image_name + '>' return json.dumps(output, sort_keys=True), 202
def get_soft_from_docker_container_id(docker_driver, container_id): # Extract Linux image distribution response = get_os_name(docker_driver.docker_exec(container_id, 'cat /etc/os-release', True, False)) if response is None: DagdaLogger.get_logger().info('Linux image distribution has not the "/etc/os-release" file. Starting the task ' 'for linux distribution identification in a blind mode ...') products = get_os_software_packages_blind_mode(docker_driver, container_id) else: # Get all installed packages if 'Red Hat' in response or 'CentOS' in response or 'Fedora' in response or 'openSUSE' in response: # Red Hat/CentOS/Fedora/openSUSE products = get_os_software_packages(docker_driver, container_id, 'rpm -aqi', parse_rpm_output_list) elif 'Debian' in response or 'Ubuntu' in response: # Debian/Ubuntu products = get_os_software_packages(docker_driver, container_id, 'dpkg -l', parse_dpkg_output_list) elif 'Alpine' in response: # Alpine products = get_os_software_packages(docker_driver, container_id, 'apk -v info', parse_apk_output_list) else: raise DagdaError('Linux image distribution not supported yet.') # Return packages return products