diff --git a/Atomic/atomic.py b/Atomic/atomic.py index 263dae0..0a30e99 100644 --- a/Atomic/atomic.py +++ b/Atomic/atomic.py @@ -687,17 +687,7 @@ class Atomic(object): return True - def images(self): - def split_repo_tags(_images): - sub_list = [item.split(":") for sublist in _images for item - in sublist['RepoTags']] - repo_tags = [] - for repo in sub_list: - if len(repo) > 2: - repo = [repo[0] + repo[1], repo[2]] - repo_tags.append(repo) - return repo_tags - + def display_all_image_info(self): def get_col_lengths(_images): ''' Determine the max length of the repository and tag names @@ -705,7 +695,7 @@ class Atomic(object): :return: a set with len of repository and tag If there are no images, return 1, 1 ''' - repo_tags = split_repo_tags(_images) + repo_tags = [[i["repo"], i["tag"]] for i in _images] # Integer additions below are for column padding # 7 == 1 for dangling, 2 for spacing, 4 for highlighting if repo_tags: @@ -714,12 +704,9 @@ class Atomic(object): else: return 1, 1 - _images = self.get_images(get_all=self.args.all) - - used_image_ids = [x['ImageID'] for x in self.get_containers()] + _images = self.images() if len(_images) >= 0: - vuln_ids = self.get_vulnerable_ids() _max_repo, _max_tag = get_col_lengths(_images) if self.args.truncate: _max_id = 14 @@ -737,7 +724,42 @@ class Atomic(object): "CREATED", "VIRTUAL SIZE", "TYPE")) + for image in _images: + if self.args.filter: + image_info = {"repo" : image['repo'], "tag" : image['tag'], "id" : image['id'], + "created" : image['created'], "size" : image['virtual_size'], "type" : image['type']} + if not self._filter_include_image(image_info): + continue + if self.args.quiet: + util.write_out(image['id']) + + else: + indicator = "" + if image["is_dangling"]: + indicator += "*" + elif image["used_image"]: + indicator += ">" + if image["vulnerable"]: + space = " " if len(indicator) < 1 else "" + if util.is_python2: + indicator = indicator + self.skull + space + else: + indicator = indicator + str(self.skull, "utf-8") + space + util.write_out(col_out.format(indicator, image['repo'], image['tag'], image['id'], image['created'], image['virtual_size'], image['type'])) + util.write_out("") + return + + def images(self): + _images = self.get_images() + all_image_info = [] + + if len(_images) >= 0: + vuln_ids = self.get_vulnerable_ids() + all_vuln_info = json.loads(self.get_all_vulnerable_info()) + used_image_ids = [x['ImageID'] for x in self.get_containers()] + for image in _images: + image_dict = dict() repo, tag = image["RepoTags"][0].rsplit(":", 1) if "Created" in image: created = time.strftime("%F %H:%M", time.localtime(image["Created"])) @@ -748,35 +770,24 @@ class Atomic(object): else: virtual_size = "" - if self.is_dangling(repo): - indicator = "*" - elif image['Id'] in used_image_ids: - indicator = ">" - else: - indicator = "" - - if image['Id'] in vuln_ids: - space = " " if len(indicator) < 1 else "" - indicator = indicator + self.skull + space - + image_dict["is_dangling"] = self.is_dangling(repo) + image_dict["used_image"] = image["Id"] in used_image_ids + image_dict["vulnerable"] = image["Id"] in vuln_ids image_id = image["Id"][:12] if self.args.truncate else image["Id"] image_type = image['ImageType'] - if self.args.filter: - image_info = {"repo" : repo, "tag" : tag, "id" : image_id, - "created" : created, "size" : virtual_size, "type" : image_type} - if not self._filter_include_image(image_info): - continue - - if self.args.quiet: - util.write_out(image_id) + image_dict["repo"] = repo + image_dict["tag"] = tag + image_dict["id"] = image_id + image_dict["created"] = created + image_dict["virtual_size"] = virtual_size + image_dict["type"] = image_type + if image_dict["vulnerable"]: + image_dict["vuln_info"] = all_vuln_info[image["Id"]] else: - util.write_out(col_out.format(indicator, repo, - tag, image_id, - created, - virtual_size, - image_type)) - util.write_out("") - return + image_dict["vuln_info"] = dict() + + all_image_info.append(image_dict) + return all_image_info def _check_if_image_present(self): self.inspect = self._inspect_image() @@ -1090,6 +1101,17 @@ class Atomic(object): if self.args.debug: self.debug = True + def get_all_vulnerable_info(self): + """ + Will simply read and return the entire /var/lib/atomic/scan_summary.json + as a JSON string so it can then be parsed appropriately. + """ + try: + return open(os.path.join(self.results, "scan_summary.json"), "r").read() + except IOError: + return "{}" + + def get_vulnerable_ids(self): """ Reads in /var/lib/atomic/scan_summary.json and returns a list of all diff --git a/Atomic/scan.py b/Atomic/scan.py index eb87ab1..d6a9250 100644 --- a/Atomic/scan.py +++ b/Atomic/scan.py @@ -146,6 +146,8 @@ class Scan(Atomic): # record environment self.record_environment() + + self.write_persistent_data() finally: # unmount all the rootfs self._unmount_rootfs_in_dir() @@ -196,6 +198,11 @@ class Scan(Atomic): return scan_list + def _get_roots_path_from_bind_name(self, in_bind_name): + for _path, bind_path in self.rootfs_mappings.items(): + if bind_path == os.path.basename(os.path.split(in_bind_name)[0]): + return _path + def get_scan_data(self): results = [] json_files = self._get_json_files() @@ -251,21 +258,12 @@ class Scan(Atomic): Write results of the scan to stdout :return: None """ - def _get_roots_path_from_bind_name(in_bind_name): - for _path, bind_path in self.rootfs_mappings.items(): - if bind_path == os.path.basename(os.path.split(in_bind_name)[0]): - return _path - - persistent_data = {} json_files = self._get_json_files() for json_file in json_files: json_results = json.load(open(json_file)) uuid = os.path.basename(json_results['UUID']) if len(self.args.rootfs) == 0 \ - else _get_roots_path_from_bind_name(json_file) - - # Get data from the results for persistent use - persistent_data[uuid] = self.get_persist_data(json_results, json_file) + else self._get_roots_path_from_bind_name(json_file) name1 = uuid if len(self.args.rootfs) > 1 else self._get_input_name_for_id(uuid) if len(self.args.rootfs) == 0 and not self._is_iid(uuid): @@ -305,9 +303,6 @@ class Scan(Atomic): .format(' ' * 5, self._get_input_name_for_id(uuid))) util.write_out("\nFiles associated with this scan are in {}.\n".format(self.results_dir)) - self.write_persistent_data(persistent_data) - - def _output_custom(self, value, indent): space = ' ' * indent next_indent = indent + 2 @@ -460,7 +455,15 @@ class Scan(Atomic): persist['json_file'] = json_file return persist - def write_persistent_data(self, new_data): + + def write_persistent_data(self): + new_data = dict() + json_files = self._get_json_files() + for json_file in json_files: + json_results = json.load(open(json_file)) + uuid = os.path.basename(json_results['UUID']) if len(self.args.rootfs) == 0 \ + else self._get_roots_path_from_bind_name(json_file) + new_data[uuid] = self.get_persist_data(json_results, json_file) summary_file = os.path.join(self.results, "scan_summary.json") if not os.path.exists(summary_file): persistent_data = new_data @@ -473,10 +476,9 @@ class Scan(Atomic): persistent_data[uuid] = new_data[uuid] # Clean up old data - for uuid in persistent_data.keys(): + for uuid in list(persistent_data): if uuid not in iids and uuid not in cids: del persistent_data[uuid] with open(summary_file, 'w') as f: json.dump(persistent_data, f, indent=4) - diff --git a/Atomic/util.py b/Atomic/util.py index bb600c9..2809516 100644 --- a/Atomic/util.py +++ b/Atomic/util.py @@ -144,7 +144,9 @@ def _output(fd, output, lf): fd.flush() if is_python2: - fd.write(output.encode('utf-8') + lf) + if isinstance(output, unicode): #pylint: disable=undefined-variable,unicode-builtin + output = output.encode('utf-8') + fd.write(output + lf) else: fd.write(output + str(lf)) diff --git a/atomic b/atomic index e560dc3..e7e07ff 100755 --- a/atomic +++ b/atomic @@ -317,7 +317,7 @@ def create_parser(help_text): help=_("list container images on your system"), epilog="atomic images by default will list all installed " "container images on your system.") - list_parser.set_defaults(func='images') + list_parser.set_defaults(func='display_all_image_info') list_parser.add_argument("-a", "--all", dest="all", default=False, action="store_true", diff --git a/atomic_client.py b/atomic_client.py index 76208fe..65b4961 100644 --- a/atomic_client.py +++ b/atomic_client.py @@ -69,6 +69,14 @@ class AtomicDBus (object): def update(self, image): self.dbus_object.Update(image, dbus_interface="org.atomic", timeout = 2147400) + @polkit.enable_proxy + def images(self): + return self.dbus_object.Images(dbus_interface="org.atomic", timeout = 2147400) + + @polkit.enable_proxy + def vulnerable(self): + return self.dbus_object.VulnerableInfo(dbus_interface="org.atomic", timeout = 2147400) + #For outputting the list of scanners def print_scan_list(all_scanners): if len(all_scanners) == 0: @@ -150,6 +158,12 @@ if __name__ == "__main__": elif(sys.argv[1] == "update"): dbus_proxy.update(sys.argv[2]) - + + elif(sys.argv[1] == "images"): + print(json.loads(dbus_proxy.images())) + + elif(sys.argv[1] == "vulnerable"): + print(json.loads(dbus_proxy.vulnerable())) + except dbus.DBusException as e: print (e) diff --git a/atomic_dbus.py b/atomic_dbus.py index 287ea84..2dfd203 100755 --- a/atomic_dbus.py +++ b/atomic_dbus.py @@ -43,6 +43,9 @@ class atomic_dbus(slip.dbus.service.Object): self.images = False self.containers = False self.container = False + self.prune = False + self.heading = False + self.truncate = False def __init__(self, *p, **k): super(atomic_dbus, self).__init__(*p, **k) @@ -244,6 +247,23 @@ class atomic_dbus(slip.dbus.service.Object): self.atomic.set_args(args) self.atomic.update() + # The Images method will list all installed container images on the system. + @slip.dbus.polkit.require_auth("org.atomic.read") + @dbus.service.method("org.atomic", in_signature='', out_signature='s') + def Images(self): + args = self.Args() + self.atomic.set_args(args) + return json.dumps(self.atomic.images()) + + # The Vulnerable method will send back information that says + # whether or not an installed container image is vulnerable + @slip.dbus.polkit.require_auth("org.atomic.read") + @dbus.service.method("org.atomic", in_signature='', out_signature='s') + def VulnerableInfo(self): + args = self.Args() + self.atomic.set_args(args) + return self.atomic.get_all_vulnerable_info() + if __name__ == "__main__": mainloop = GLib.MainLoop() diff --git a/tests/atomic-client.js b/tests/atomic-client.js new file mode 100644 index 0000000..366853e --- /dev/null +++ b/tests/atomic-client.js @@ -0,0 +1,167 @@ +require([ + "jquery", + "base1/cockpit", +], function($, cockpit) { + var input = $("#new"); + var service = cockpit.dbus("org.atomic"); + var proxy = service.proxy("org.atomic", "/org/atomic/object"); + proxy.wait(function () { + if (!proxy.valid) { + $('#ui').hide(); + $('#curtain').show(); + } + else { + run_scan_list(); + } + $('body').show(); + }); + + $("#Run").on("click", run_request); + function run_scan_list() { + var call = proxy.ScanList(); + call.done(function(result) { + response = JSON.parse(result); + for (var i = 0; i < response.length; i++) { + var radio = document.createElement('input'); + radio.type = "radio"; + radio.setAttribute("name", "scanner"); + radio.setAttribute("value", response[i]["scanner_name"]); + var label = document.createElement('label') + label.htmlFor = "id"; + label.appendChild(document.createTextNode(response[i]["scanner_name"])); + document.body.appendChild(radio); + document.body.appendChild(label); + scanned_list = response[i]["scans"]; + for (var j = 0; j < scanned_list.length; j++) { + var radio_type = document.createElement('input'); + radio_type.type = "radio"; + radio_type.setAttribute("name", "scan_type"); + radio_type.setAttribute("value", scanned_list[j]["name"]); + label = document.createElement('label') + label.htmlFor = "id"; + label.appendChild(document.createTextNode(scanned_list[j]["name"])); + document.body.appendChild(radio_type); + document.body.appendChild(label); + } + } + run_images(); + }); + + call.fail(function(error) { + console.warn(error); + }); + } + + function run_request() { + var scan_targets = []; + $('input[name="image"]:checked').each(function() { + scan_targets.push($(this).val()); + }); + if(typeof scan_targets == "undefined") { + scan_targets = []; + } + var scanner = $('input[name="scanner"]:checked').val(); + if(typeof scanner == "undefined") { + scanner = ''; + } + var scan_type = $('input[name="scan_type"]:checked').val(); + if(typeof scan_type == "undefined") { + scan_type = ''; + } + run_scan(scan_targets, scanner, scan_type) + } + + function run_scan_async(scan_targets, scanner, scan_type) { + var call = proxy.ScheduleScan(scan_targets, scanner, scan_type, rootfs, false, false, false); + call.done(function(result) { + while(true){ + NewCall = proxy.GetScanResults(result); + NewCall.done(function(data) { + if(data.length > 0) { + console.log(data); + } + }); + } + }); + + call.fail(function(error) { + console.warn(error); + }); + } + + function run_scan(scan_targets, scanner, scan_type) { + var call; + call = proxy.Scan(scan_targets, scanner, scan_type, [], false, false, false) + call.done(function(result) { + var label = document.createElement('label') + label.htmlFor = "id"; + label.appendChild(document.createTextNode(JSON.stringify(result))); + document.body.appendChild(label); + }); + + call.fail(function(error) { + console.warn(error); + }); + } + + function run_vulnerable_info() { + var call = proxy.VulnerableInfo(); + call.done(function(result) { + console.log(result); + }); + + call.fail(function(error) { + console.warn(error); + }); + } + + function run_update(image) { + var call = proxy.Update(image); + call.done(function() { + console.log("Success"); + }); + + call.fail(function(error) { + console.warn(error); + }); + } + + function run_images() { + var call = proxy.Images(); + var text = "Repository Last Scanned\n"; + call.done(function(result) { + response = JSON.parse(result); + for (var i = 2; i < response.length; i++) { + text += response[i]["repo"] + " "; + var checkbox = document.createElement('input'); + checkbox.type = "checkbox"; + checkbox.setAttribute("name", "image"); + checkbox.setAttribute("value", response[i]["repo"]); + var label = document.createElement('label') + label.htmlFor = "id"; + label.appendChild(document.createTextNode(response[i]["repo"])); + document.body.appendChild(checkbox); + document.body.appendChild(label); + if ("Time" in response[i]["vuln_info"]) { + text += response[i]["vuln_info"]["Time"] + " "; + } + + if(response[i]["vulnerable"]) { + text += "*\n"; + } + + else { + text += "\n"; + } + } + label = document.createElement('label'); + label.htmlFor = "id"; + label.appendChild(document.createTextNode(text)); + document.body.appendChild(label); + }); + + call.fail(function(error) { + console.warn(error); + }); + } +}); diff --git a/tests/atomicClient.html b/tests/atomicClient.html index a984f69..59129c0 100644 --- a/tests/atomicClient.html +++ b/tests/atomicClient.html @@ -4,6 +4,8 @@ + +
@@ -12,10 +14,6 @@ - - - - @@ -26,247 +24,5 @@ - - diff --git a/tests/manifest.json b/tests/manifest.json index 90a1073..4290619 100644 --- a/tests/manifest.json +++ b/tests/manifest.json @@ -6,7 +6,5 @@ "label": "Atomic", "path": "atomicClient.html" } - }, - - "content-security-policy": "default-src 'self' 'unsafe-inline' 'unsafe-eval'" + } }