def pull_image(self, image, remote_image_obj, **kwargs): assert(isinstance(remote_image_obj, Image)) debug = kwargs.get('debug', False) if image.startswith("dockertar:"): path = image.replace("dockertar:", "", 1) with open(path, 'rb') as f: self.d.load_image(data=f) return 0 fq_name = remote_image_obj.fq_name local_image = self.has_image(image) if local_image is not None: if self.already_has_image(local_image, remote_image_obj): raise util.ImageAlreadyExists(image) registry, _, _, tag, _ = util.Decompose(fq_name).all image = "docker-daemon:{}".format(fq_name) if not image.endswith(tag): image += ":{}".format(tag) if '@sha256:' in image: image = image.replace("@sha256:", ":") src_creds = kwargs.get('src_creds') insecure = True if util.is_insecure_registry(self.d.info()['RegistryConfig'], registry) else False trust = Trust() trust.discover_sigstore(fq_name) util.write_out("Pulling {} ...".format(fq_name)) util.skopeo_copy("docker://{}".format(fq_name), image, debug=debug, insecure=insecure, policy_filename=trust.policy_filename, src_creds=src_creds) return 0
def pull_image(self, image, remote_image_obj, **kwargs): """ Pulls an image to the backend :param image: :param pull_args: :return: """ debug = kwargs.get('debug', False) fq_name = remote_image_obj.fq_name registry, _, _, tag, _ = util.Decompose(fq_name).all if not image.endswith(tag): image += ":{}".format(tag) if '@sha256:' in image: image = image.replace("@sha256:", ":") insecure = False registries_config = util.load_registries_from_yaml() if "insecure_registries" in registries_config: if registry in registries_config['insecure_registries']: insecure = True source = "docker://{}".format(image) dest = "containers-storage:{}".format(image) trust = Trust() trust.discover_sigstore(fq_name) util.write_out("Pulling {} ...".format(fq_name)) util.skopeo_copy(source, dest, debug=debug, insecure=insecure, policy_filename=trust.policy_filename) return 0
def pull_image(self, image, **kwargs): debug = kwargs.get("debug", False) if image.startswith("dockertar:"): path = image.replace("dockertar:", "", 1) with open(path, "rb") as f: self.d.load_image(data=f) return 0 remote_image = self.make_remote_image(image) fq_name = remote_image.fq_name local_image = self.has_image(image) if local_image is not None: if self.already_has_image(local_image, remote_image): raise ValueError("Latest version of {} already present.".format(image)) registry, _, _, tag, _ = util.Decompose(fq_name).all image = "docker-daemon:{}".format(image) if not image.endswith(tag): image += ":{}".format(tag) insecure = ( True if util.is_insecure_registry(self.d.info()["RegistryConfig"], util.strip_port(registry)) else False ) trust = Trust() trust.discover_sigstore(fq_name) util.write_out("Pulling {} ...".format(fq_name)) util.skopeo_copy( "docker://{}".format(fq_name), image, debug=debug, insecure=insecure, policy_filename=trust.policy_filename ) return 0
def _running(self, con_obj, args, atomic): requested_image = self.has_image(args.image) if requested_image is not None and con_obj.image != requested_image.id: requested_image_fq_name = requested_image.fq_name raise AtomicError("Warning: container '{}' already points to {}\nRun 'atomic run {}' to run " "the existing container.\nRun 'atomic run --replace '{}' to replace " "it".format(con_obj.name, con_obj.original_structure['Config']['Image'], con_obj.name, requested_image_fq_name)) if con_obj.interactive: container_command = con_obj.command if not args.command else args.command container_command = container_command if not isinstance(container_command, list) else " ".join(container_command) cmd = [atomic.docker_binary(), "exec", "-t", "-i", con_obj.name] + container_command.split() if args.display: return atomic.display(" ".join(cmd)) else: return util.check_call(cmd, stderr=DEVNULL) else: command = con_obj.command if not args.command else args.command try: cmd = [atomic.docker_binary(), "exec", "-t", "-i", con_obj.name] + command except TypeError: cmd = [atomic.docker_binary(), "exec", "-t", "-i", con_obj.name] + command.split() if args.command: if args.display: return util.write_out(" ".join(cmd)) else: return util.check_call(cmd, stderr=DEVNULL) else: if not args.display: util.write_out("Container is running")
def replace_existing_container(self, _iobject, _requested_image, _args): if _args.debug: util.write_out("Removing the container {} and running with {}".format(_iobject.name, _requested_image.fq_name)) self.delete_container(_iobject.id, force=True) _iobject = _requested_image if _args.command: _iobject.user_command = _args.command return _iobject
def update(self): if self.args.debug: write_out(str(self.args)) beu = BackendUtils() try: be, img_obj = beu.get_backend_and_image_obj(self.image, str_preferred_backend=self.args.storage or storage, required=True if self.args.storage else False) input_name = img_obj.input_name except ValueError: raise ValueError("{} not found locally. Unable to update".format(self.image)) be.update(input_name, debug=self.args.debug, force=self.args.force, image_object=img_obj) return 0
def _running(self, con_obj, args, atomic): if con_obj.interactive: cmd = [atomic.docker_binary(), "exec", "-t", "-i", con_obj.name, con_obj.command] if args.display: return atomic.display(cmd) else: return util.check_call(cmd, stderr=DEVNULL) else: cmd = [atomic.docker_binary(), "exec", "-t", "-i", con_obj.name] + con_obj.command if args.command: if args.display: return util.write_out(" ".join(cmd)) else: return util.check_call(cmd, stderr=DEVNULL) else: if not args.display: util.write_out("Container is running")
def pull_image(self, image, pull_args): # Add this when atomic registry is incorporated. # if self.args.reg_type == "atomic": # pull_uri = 'atomic:' # else: # pull_uri = 'docker://' img_obj = self._make_remote_image(image) fq_name = img_obj.fq_name insecure = True if util.is_insecure_registry(self.d.info()['RegistryConfig'], util.strip_port(img_obj.registry)) else False # This needs to be re-enabled with Aaron's help trust = Trust() trust.set_args(pull_args) trust.discover_sigstore(fq_name) util.write_out("Pulling {} ...".format(fq_name)) util.skopeo_copy("docker://{}".format(fq_name), "docker-daemon:{}".format(image), debug=pull_args.debug, insecure=insecure, policy_filename=pull_args.policy_filename)
def _start(self, con_obj, args, atomic): exec_error = "Failed to execute the command inside the existing container. In some situations " \ "this can happen because the entry point command of the container only runs for " \ "a short time. You might want to replace the container by executing your " \ "command with --replace. Note any updates to the existing container will be lost" if con_obj.interactive: if args.command: util.check_call([atomic.docker_binary(), "start", con_obj.name], stderr=DEVNULL) container_command = args.command if isinstance(args.command, list) else args.command.split() try: return util.check_call([atomic.docker_binary(), "exec", "-t", "-i", con_obj.name] + container_command) except CalledProcessError as e: if args.debug: util.write_out(str(e)) raise AtomicError(exec_error) else: return util.check_call( [atomic.docker_binary(), "start", "-i", "-a", con_obj.name], stderr=DEVNULL) else: if args.command: util.check_call( [atomic.docker_binary(), "start", con_obj.name], stderr=DEVNULL) try: return util.check_call( [atomic.docker_binary(), "exec", "-t", "-i", con_obj.name] + con_obj.command) except CalledProcessError as e: if args.debug: util.write_out(str(e)) raise AtomicError(exec_error) else: return util.check_call( [atomic.docker_binary(), "start", con_obj.name], stderr=DEVNULL)
def check_args(cmd): found_sec_arg = False security_args = { "--privileged": "This container runs without separation and should be " "considered the same as root on your system.", "--cap-add": "Adding capabilities to your container could allow processes " "from the container to break out onto your host system.", "--security-opt label:disable": "Disabling label separation turns off tools like SELinux and " "could allow processes from the container to break out onto " "your host system.", "--net=host": "Processes in this container can listen to ports (and " "possibly rawip traffic) on the host's network.", "--pid=host": "Processes in this container can see and interact with all " "processes on the host and disables SELinux within the " "container.", "--ipc=host": "Processes in this container can see and possibly interact " "with all semaphores and shared memory segments on the host " "as well as disables SELinux within the container.", } for sec_arg in security_args: if sec_arg in cmd: if not found_sec_arg: util.write_out("\nThis container uses privileged " "security switches:") util.write_out("\n\033[1mINFO: {}\033[0m " "\n{}{}".format(sec_arg, " " * 6, security_args[sec_arg])) found_sec_arg = True if found_sec_arg: util.write_out( "\nFor more information on these switches and their " "security implications, consult the manpage for " "'docker run'.\n" )
def check_args(cmd): found_sec_arg = False security_args = { '--privileged': 'This container runs without separation and should be ' 'considered the same as root on your system.', '--cap-add': 'Adding capabilities to your container could allow processes ' 'from the container to break out onto your host system.', '--security-opt label:disable': 'Disabling label separation turns off tools like SELinux and ' 'could allow processes from the container to break out onto ' 'your host system.', '--security-opt label=disable': 'Disabling label separation turns off tools like SELinux and ' 'could allow processes from the container to break out onto ' 'your host system.', '--net=host': 'Processes in this container can listen to ports (and ' 'possibly rawip traffic) on the host\'s network.', '--pid=host': 'Processes in this container can see and interact with all ' 'processes on the host and disables SELinux within the ' 'container.', '--ipc=host': 'Processes in this container can see and possibly interact ' 'with all semaphores and shared memory segments on the host ' 'as well as disables SELinux within the container.' } for sec_arg in security_args: if sec_arg in cmd: if not found_sec_arg: util.write_out("\nThis container uses privileged " "security switches:") util.write_out("\n\033[1mINFO: {}\033[0m " "\n{}{}".format(sec_arg, " " * 6, security_args[sec_arg])) found_sec_arg = True if found_sec_arg: util.write_out( "\nFor more information on these switches and their " "security implications, consult the manpage for " "'docker run'.\n")
def print_scan_list(all_scanners): if len(all_scanners) == 0: util.write_out("There are no scanners configured for this system.") sys.exit(0) default_scanner = (util.get_atomic_config())['default_scanner'] if default_scanner is None: default_scanner = '' for scanner in all_scanners: scanner_name = scanner['scanner_name'] df = '* ' if scanner_name == default_scanner else '' default_scan_type = scanner.get('default_scan') if default_scan_type is None: raise ValueError("Invalid configuration file: At least one scan type must be " "declared as the default for {}.".format(scanner_name)) util.write_out("Scanner: {} {}".format(scanner_name, df)) util.write_out("{}Image Name: {}".format(" " * 2, scanner['image_name'])) for scan_type in scanner['scans']: df = '* ' if default_scan_type == scan_type['name'] else '' util.write_out("{}Scan type: {} {}".format(" " * 5, scan_type['name'], df)) util.write_out("{}Description: {}\n".format(" " * 5, scan_type['description'])) util.write_out("\n* denotes defaults") sys.exit(0)
def run(self, iobject, **kwargs): def add_string_or_list_to_list(list_item, value): if not isinstance(value, list): value = value.split() list_item += value return list_item atomic = kwargs.get('atomic', None) args = kwargs.get('args') # atomic must be an instance of Atomic # args must be a argparse Namespace assert(isinstance(atomic, Atomic)) # The object is a container # If container exists and not started, start it # If container exists and is started, execute command inside it (docker exec) # If container doesn't exist, create one and start it if args.command: iobject.user_command = args.command if isinstance(iobject, Container): latest_image = self.inspect_image(iobject.image_name) if latest_image.id != iobject.image: util.write_out("The '{}' container is using an older version of the installed\n'{}' container image. If " "you wish to use the newer image,\nyou must either create a new container with a " "new name or\nuninstall the '{}' container. \n\n# atomic uninstall --name " "{} {}\n\nand create new container on the {} image.\n\n# atomic update --force " "{}s\n\n removes all containers based on an " "image.".format(iobject.name, iobject.image_name, iobject.name, iobject.name, iobject.image_name, iobject.image_name, iobject.image_name)) requested_image = self.has_image(args.image) if requested_image is None: requested_image = self.has_image(iobject.image) if iobject.running: if args.replace: iobject = self.replace_existing_container(iobject, requested_image, args) return self.run(iobject, args=args, atomic=atomic) return self._running(iobject, args, atomic) else: # Container with the name exists image_id = iobject.image if requested_image.id != image_id: if args.replace: iobject = self.replace_existing_container(iobject, requested_image, args) else: try: requested_image_fq_name = requested_image.fq_name except RegistryInspectError: requested_image_fq_name = args.image raise AtomicError("Warning: container '{}' already points to {}\nRun 'atomic run {}' to run " "the existing container.\nRun 'atomic run --replace '{}' to replace " "it".format(iobject.name, iobject.original_structure['Config']['Image'], iobject.name, requested_image_fq_name)) else: if args.replace: iobject = self.replace_existing_container(iobject, requested_image, args) else: return self._start(iobject, args, atomic) if iobject.get_label('INSTALL') and not args.ignore and not util.InstallData.image_installed(iobject): raise ValueError("The image '{}' appears to have not been installed and has an INSTALL label. You " "should install this image first. Re-run with --ignore to bypass this " "error.".format(iobject.name or iobject.image)) # The object is an image command = [] if iobject.run_command: command = add_string_or_list_to_list(command, iobject.run_command) if iobject.user_command: command = add_string_or_list_to_list(command, iobject.user_command) opts_file = iobject.get_label("RUN_OPTS_FILE") if opts_file: opts_file = atomic.sub_env_strings("".join(opts_file)) if opts_file.startswith("/"): if os.path.isfile(opts_file): try: atomic.run_opts = open(opts_file, "r").read() except IOError: raise ValueError("Failed to read RUN_OPTS_FILE %s" % opts_file) else: raise ValueError("Will not read RUN_OPTS_FILE %s: not absolute path" % opts_file) else: command += [atomic.docker_binary(), "run"] if os.isatty(0): command += ["-t"] if args.detach: command += ["-d"] command += atomic.SPC_ARGS if args.spc else atomic.RUN_ARGS if iobject.user_command: command = add_string_or_list_to_list(command, iobject.user_command) if len(command) > 0 and command[0] == "docker": command[0] = atomic.docker_binary() if iobject.cmd and not iobject.user_command and not iobject.run_command: cmd = iobject.cmd if isinstance(iobject.cmd, list) else iobject.cmd.split() command += cmd command = atomic.gen_cmd(command) command = atomic.sub_env_strings(command) atomic.display(command) if atomic.args.display: return if not atomic.args.quiet: self.check_args(command) return util.check_call(command, env=atomic.cmd_env())
def prune(self): for iid in self.get_dangling_images(): self.delete_image(iid, force=True) util.write_out("Removed dangling Image {}".format(iid)) return 0
def display_all_image_info(self): def get_col_lengths(_images): ''' Determine the max length of the repository and tag names :param _images: :return: a set with len of repository and tag If there are no images, return 1, 1 ''' repo_tags = [y for x in _images if x.repotags for y in x.split_repotags] if repo_tags: return max([len(x[0]) for x in repo_tags]) + 2,\ max([len(x[1]) for x in repo_tags]) + 2 else: return 1, 1 if self.args.debug: util.write_out(str(self.args)) _images = self._get_images() for i in _images: i.repo, i.tag = i.split_repotags[0] if self.args.filter: _images = [x for x in _images if self._filter_include_image(x)] if self.args.json: util.output_json(self.return_json(_images)) return 0 if len(_images) == 0: return _max_repo, _max_tag = get_col_lengths(_images) if self.args.truncate: _max_id = 14 else: _max_id = 65 col_out = "{0:2} {1:" + str(_max_repo) + "} {2:" + str(_max_tag) + \ "} {3:" + str(_max_id) + "} {4:18} {5:14} {6:10}" if self.args.heading and not self.args.quiet: util.write_out(col_out.format(" ", "REPOSITORY", "TAG", "IMAGE ID", "CREATED", "VIRTUAL SIZE", "TYPE")) for image in _images: if self.args.quiet: util.write_out(image.id) else: indicator = "" if image.is_dangling: indicator += "*" elif image.used: 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 _id = image.short_id if self.args.truncate else image.id util.write_out(col_out.format(indicator, image.repo or "<none>", image.tag or "<none>", _id, image.timestamp, image.virtual_size, image.backend.backend)) util.write_out("") return
def dump_backends(self): backends = '' for i in self.available_backends: be = i() backends += "{}: Active, ".format(be.backend) write_out("Backends({})\n".format(backends))
def message_backend_change(previous, new): write_out("\nNote: Switching from the '{}' backend to the '{}' backend based on the 'atomic.type' label in the " "image. You can use --storage to override this behaviour.\n".format(previous, new))
def display_all_image_info(self): def get_col_lengths(_images): ''' Determine the max length of the repository and tag names :param _images: :return: a set with len of repository and tag If there are no images, return 1, 1 ''' repo_tags = [ y for x in _images if x.repotags for y in x.split_repotags ] if repo_tags: return max([len(x[0]) for x in repo_tags]) + 2,\ max([len(x[1]) for x in repo_tags]) + 2 else: return 1, 1 if self.args.debug: util.write_out(str(self.args)) _images = self._get_images() if self.args.json: util.output_json(self.return_json(_images)) return 0 if len(_images) == 0: return _max_repo, _max_tag = get_col_lengths(_images) if self.args.truncate: _max_id = 14 else: _max_id = 65 col_out = "{0:2} {1:" + str(_max_repo) + "} {2:" + str(_max_tag) + \ "} {3:" + str(_max_id) + "} {4:18} {5:14} {6:10}" if self.args.heading and not self.args.quiet: util.write_out( col_out.format(" ", "REPOSITORY", "TAG", "IMAGE ID", "CREATED", "VIRTUAL SIZE", "TYPE")) for image in _images: if self.args.filter: if not self._filter_include_image(image): continue if self.args.quiet: util.write_out(image.id) else: indicator = "" if image.is_dangling: indicator += "*" elif image.used: 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 repo, tag = image.split_repotags[0] _id = image.short_id if self.args.truncate else image.id util.write_out( col_out.format(indicator, repo or "<none>", tag or "<none>", _id, image.timestamp, image.virtual_size, image.backend.backend)) util.write_out("") return
def print_scan_list(all_scanners): if len(all_scanners) == 0: util.write_out("There are no scanners configured for this system.") sys.exit(0) default_scanner = (util.get_atomic_config())['default_scanner'] if default_scanner is None: default_scanner = '' for scanner in all_scanners: scanner_name = scanner['scanner_name'] df = '* ' if scanner_name == default_scanner else '' default_scan_type = scanner.get('default_scan') if default_scan_type is None: raise ValueError( "Invalid configuration file: At least one scan type must be " "declared as the default for {}.".format(scanner_name)) util.write_out("Scanner: {} {}".format(scanner_name, df)) util.write_out("{}Image Name: {}".format(" " * 2, scanner['image_name'])) for scan_type in scanner['scans']: df = '* ' if default_scan_type == scan_type['name'] else '' util.write_out("{}Scan type: {} {}".format(" " * 5, scan_type['name'], df)) util.write_out("{}Description: {}\n".format( " " * 5, scan_type['description'])) util.write_out("\n* denotes defaults") sys.exit(0)
def display_all_image_info(self): def get_col_lengths(_images): ''' Determine the max length of the repository and tag names :param _images: :return: a set with len of repository and tag If there are no images, return 1, 1 ''' repo_tags = [[i["repo"], i["tag"]] for i in _images] if repo_tags: return max([len(x[0]) for x in repo_tags]) + 2,\ max([len(x[1]) for x in repo_tags]) + 2 else: return 1, 1 _images = self.images() if self.args.json: json.dump(_images, sys.stdout) return if len(_images) >= 0: _max_repo, _max_tag = get_col_lengths(_images) if self.args.truncate: _max_id = 14 else: _max_id = 65 col_out = "{0:2} {1:" + str(_max_repo) + "} {2:" + str(_max_tag) + \ "} {3:" + str(_max_id) + "} {4:18} {5:14} {6:10}" if self.args.heading and not self.args.quiet: util.write_out( col_out.format(" ", "REPOSITORY", "TAG", "IMAGE ID", "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'], "dangling": "{}".format(image['is_dangling']) } 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 run(self, iobject, **kwargs): def add_string_or_list_to_list(list_item, value): if not isinstance(value, list): value = value.split() list_item += value return list_item atomic = kwargs.get('atomic', None) args = kwargs.get('args') # atomic must be an instance of Atomic # args must be a argparse Namespace assert(isinstance(atomic, Atomic)) # The object is a container # If container exists and not started, start it # If container exists and is started, execute command inside it (docker exec) # If container doesn't exist, create one and start it if args.command: iobject.user_command = args.command if isinstance(iobject, Container): latest_image = self.inspect_image(iobject.image_name) if latest_image.id != iobject.image: util.write_out("The '{}' container is using an older version of the installed\n'{}' container image. If " "you wish to use the newer image,\nyou must either create a new container with a " "new name or\nuninstall the '{}' container. \n\n# atomic uninstall --name " "{} {}\n\nand create new container on the {} image.\n\n# atomic update --force " "{}s\n\n removes all containers based on an " "image.".format(iobject.name, iobject.image_name, iobject.name, iobject.name, iobject.image_name, iobject.image_name, iobject.image_name)) if iobject.running: return self._running(iobject, args, atomic) else: return self._start(iobject, args, atomic) if iobject.get_label('INSTALL') and not args.ignore and not util.InstallData.image_installed(iobject): raise ValueError("The image '{}' appears to have not been installed and has an INSTALL label. You " "should install this image first. Re-run with --ignore to bypass this " "error.".format(iobject.name or iobject.image)) # The object is an image command = [] if iobject.run_command: command = add_string_or_list_to_list(command, iobject.run_command) if iobject.user_command: command = add_string_or_list_to_list(command, iobject.user_command) opts_file = iobject.get_label("RUN_OPTS_FILE") if opts_file: opts_file = atomic.sub_env_strings("".join(opts_file)) if opts_file.startswith("/"): if os.path.isfile(opts_file): try: atomic.run_opts = open(opts_file, "r").read() except IOError: raise ValueError("Failed to read RUN_OPTS_FILE %s" % opts_file) else: raise ValueError("Will not read RUN_OPTS_FILE %s: not absolute path" % opts_file) else: command += [atomic.docker_binary(), "run"] if os.isatty(0): command += ["-t"] if args.detach: command += ["-d"] command += atomic.SPC_ARGS if args.spc else atomic.RUN_ARGS if iobject.user_command: command = add_string_or_list_to_list(command, iobject.user_command) if len(command) > 0 and command[0] == "docker": command[0] = atomic.docker_binary() if iobject.cmd and not iobject.user_command and not iobject.run_command: cmd = iobject.cmd if isinstance(iobject.cmd, list) else iobject.cmd.split() command += cmd command = atomic.gen_cmd(command) command = atomic.sub_env_strings(command) atomic.display(command) if atomic.args.display: return if not atomic.args.quiet: self.check_args(command) return util.check_call(command, env=atomic.cmd_env())