def delete_vuln_resources_in_k8s(vuln, verbose=False): """Delete resources related to one vulnerability. Delete resources related to one vulnerability specified by args.vuln from the current Kubernetes cluster. Args: vuln: Information dict about one vulnerability and its resources' locations. verbose: Verbose or not. Returns: None. """ color_print.debug( '{vuln} is going to be removed'.format(vuln=vuln['name'])) yamls = [ os.path.join(vuln['path'], dependency) for dependency in vuln['dependencies']['yamls'] ] if not KubernetesResourceDeployer.delete(yamls, verbose=verbose): color_print.error('failed to remove {v}'.format(v=vuln['name'])) else: # remove port record if applicable yamls_svc = [yaml for yaml in yamls if yaml.endswith('-service.yaml')] if yamls_svc: # release ports not used any more port_manager.release_ports(yamls_svc) color_print.debug('{v} successfully removed'.format(v=vuln['name']))
def uninstall(cls, verbose=False): """Uninstall Kata-containers. Args: verbose: Verbose or not. Returns: None. """ # currently, metarget only supports docker # in the future more CRIs will be supported # 1. configure /etc/docker/daemon.json if not cls._configure_docker_with_kata( base_dir=config.kata_tar_decompress_dest, recover=True): color_print.error( 'failed to remove kata-containers configurations') return False # 2. reload daemon configurations and restart docker if not cls.reload_and_restart_docker(verbose=verbose): return False # 3. remove /etc/kata-containers/ color_print.debug( 'removing {kata_config_dir}'.format( kata_config_dir=config.kata_config_dir)) rmtree(path=config.kata_config_dir, ignore_errors=True) # 4. remove /opt/kata/ color_print.debug('removing {kata_dst}'.format( kata_dst=config.kata_tar_decompress_dest)) rmtree(path=config.kata_tar_decompress_dest, ignore_errors=True) return True
def remove(args): """Remove an installed cloud native gadget. Args: args.gadget: Name of the specified cloud native gadget. args.verbose: Verbose or not. Returns: None. """ if args.gadget == 'docker': DockerInstaller.uninstall(verbose=args.verbose) color_print.debug( '{gadget} successfully removed'.format(gadget=args.gadget)) if args.gadget == 'k8s': KubernetesInstaller.uninstall(verbose=args.verbose) color_print.debug( '{gadget} successfully removed'.format(gadget=args.gadget)) if args.gadget == 'kata': if KataContainersInstaller.uninstall(verbose=args.verbose): color_print.debug( '{gadget} successfully removed'.format(gadget=args.gadget)) else: color_print.error( 'failed to remove {gadget}'.format(gadget=args.gadget)) if args.gadget == 'kernel': color_print.warning( 'removal of {gadget} is unsupported'.format(gadget=args.gadget))
def reload_and_restart_docker(verbose=False): """Reload configurations and restart Docker. systemctl daemon-reload && systemctl restart docker Args: verbose: Verbose or not. Returns: Boolean indicating whether configurations is successfully reload and Docker is successfully restarted or not. """ # reload docker daemon configurations if not system_func.reload_daemon_config(verbose=verbose): return False stdout, stderr = verbose_func.verbose_output(verbose) color_print.debug('restarting docker') try: subprocess.run('systemctl restart docker'.split(), stdout=stdout, stderr=stderr, check=True) return True except subprocess.CalledProcessError: color_print.error('failed to restart docker') return False
def reboot_system(verbose=False): stdout, stderr = verbose_func.verbose_output(verbose) cmd_reboot = 'init 6'.split() try: subprocess.run(cmd_reboot, stdout=stdout, stderr=stderr, check=True) except subprocess.CalledProcessError: color_print.error('failed to reboot system') return False
def remove(args): """Remove an installed cloud native vulnerability. Remove the vulnerable cloud native gadget with the cloud native vulnerability specified by args.cnv. Args: args.cnv: Name of the specified cloud native vulnerability. args.verbose: Verbose or not. Returns: None. """ vulns = vuln_loader.load_vulns_by_dir(config.vuln_cn_dir_wildcard) vuln = filters.filter_vuln_by_name(vulns=vulns, name=args.cnv) if not vuln: color_print.error_and_exit( 'no cloud native vulnerability named {cnv}'.format(cnv=args.cnv)) if vuln['class'] == 'config' or vuln['class'] == 'mount' or vuln[ 'class'] == 'no-vuln': vulns = vuln_loader.load_vulns_by_dir(config.vuln_cn_dir_wildcard) vuln = filters.filter_vuln_by_name(vulns=vulns, name=args.cnv) if not vuln: color_print.error_and_exit( 'no vulnerability named {cnv}'.format(cnv=args.cnv)) internal_cmds.delete_vuln_resources_in_k8s(vuln, verbose=args.verbose) return color_print.debug( '{vuln} is going to be removed'.format(vuln=vuln['name'])) if vuln['class'].startswith('docker'): DockerInstaller.uninstall(verbose=args.verbose) color_print.debug('{v} successfully removed'.format(v=vuln['name'])) if vuln['class'] == 'kubernetes': KubernetesInstaller.uninstall(verbose=args.verbose) color_print.debug('{v} successfully removed'.format(v=vuln['name'])) if vuln['class'] == 'kata-containers': if KataContainersInstaller.uninstall(verbose=args.verbose): color_print.debug( '{v} successfully removed'.format(v=vuln['name'])) else: color_print.error('failed to remove {v}'.format(v=vuln['name'])) if vuln['class'] == 'kernel': color_print.warning( 'removal of vulnerabilities in class {vuln_class} is unsupported'. format(vuln_class=vuln['class'])) return
def reload_daemon_config(verbose=False): color_print.debug('reloading daemon configurations') stdout, stderr = verbose_func.verbose_output(verbose) try: subprocess.run('systemctl daemon-reload'.split(), stdout=stdout, stderr=stderr, check=True) return True except subprocess.CalledProcessError: color_print.error('failed to reload daemon configurations') return False
def deploy_vuln_resources_in_k8s(vuln, external=False, verbose=False): """Deploy resources related to one vulnerability. Deploy resources related to one vulnerability specified by args.vuln in the current Kubernetes cluster. Args: vuln: Information dict about one vulnerability and its resources' locations. external: Expose service through NodePort or not (ClusterIP by default).. verbose: Verbose or not. Returns: None. """ color_print.debug( '{vuln} is going to be installed'.format( vuln=vuln['name'])) yamls = [os.path.join(vuln['path'], dependency) for dependency in vuln['dependencies']['yamls']] # if services need to be exposed externally, modify yaml # and change type from ClusterIP to NodePort if external: yamls_svc = [ temp_yaml for temp_yaml in yamls if temp_yaml.endswith('-service.yaml')] if yamls_svc: # remove services from yamls yamls = [ temp_yaml for temp_yaml in yamls if not temp_yaml.endswith('-service.yaml')] # allocate ports on host host_ports = port_manager.allocate_ports(entries=yamls_svc) # generate new yamls using nodeport in svc yamls new_yamls_svc = resource_modifier.generate_svcs_with_clusterip_to_nodeport( yamls=yamls_svc, ports=host_ports) # add updated services into original yamls yamls.extend(new_yamls_svc) # create namespace metarget in k8s if it is not created yet if not KubernetesResourceDeployer.apply( resources_list=[config.k8s_metarget_namespace_file], verbose=verbose): color_print.error_and_exit( 'failed to create namespace {nm}'.format( nm=config.k8s_metarget_namespace)) if not KubernetesResourceDeployer.apply( resources_list=yamls, verbose=verbose): color_print.error( 'failed to install {v}'.format( v=vuln['name'])) else: color_print.debug('{v} successfully installed'.format(v=vuln['name']))
def _act(cls, resources_list, action=None, verbose=False): stdout, stderr = verbose_func.verbose_output(verbose) cmd_kubectl_create = 'kubectl {action} -f'.format( action=action).split() for res in resources_list: color_print.debug('{action}ing {res}'.format( action=action.strip('e'), res=res)) temp_cmd = copy.copy(cmd_kubectl_create) temp_cmd.append(res) try: subprocess.run(temp_cmd, stdout=stdout, stderr=stderr, check=True) except subprocess.CalledProcessError: color_print.error( 'failed to {action} resources in {res}'.format( action=action, res=res)) return False return True
def _fetch_package_list_by_version(cls, version, verbose=False): color_print.debug('retrieving package list for kernel %s' % version) try: f = open(config.kernel_packages_list, 'r') except FileNotFoundError: color_print.warning('%s does not exist.' % config.kernel_packages_list) package_list_downloader.download_package_list() f = open(config.kernel_packages_list, 'r') try: packages = yaml.load(f, Loader=yaml.SafeLoader) for key in packages.keys(): if version in key: color_print.debug('kernel package list found:') color_print.debug(json.dumps(packages[key])) return packages[key] except yaml.scanner.ScannerError: return None color_print.error('kernel package list not found') return None
def _run_kubeadm(cls, k8s_version, context, mappings=None, verbose=False): color_print.debug('running kubeadm') stdout, stderr = verbose_func.verbose_output(verbose) temp_cmd = 'kubeadm init'.split() temp_cmd.append('--kubernetes-version={k8s_version}'.format( k8s_version=k8s_version)) temp_cmd.append(cls._kubeadm_common_options) if mappings: mappings['kubeadm_options'] = cls._kubeadm_common_options pod_network_cidr = context.get('pod_network_cidr', None) if pod_network_cidr: temp_cmd.append( '--pod-network-cidr={cidr}'.format(cidr=pod_network_cidr)) try: subprocess.run(temp_cmd, stdout=stdout, stderr=stderr, check=True, env=context.get('envs', None)) return True except subprocess.CalledProcessError: color_print.error('failed to run kubeadm') return False
def install_by_version(cls, gadgets, context=None, verbose=False): """Install Docker with specified version. Args: gadgets: Docker gadgets (e.g. docker-ce). context: Currently not used. verbose: Verbose or not. Returns: Boolean indicating whether Docker is successfully installed or not. """ if not cls._pre_install(verbose=verbose): color_print.error('failed to install prerequisites') return False for gadget in gadgets: if not cls._install_one_gadget_by_version( gadget['name'], gadget['version'], verbose=verbose): color_print.warning( 'docker seems to be installed, but some errors happened during installation') # sometimes docker is installed but error occurs during installation # so currently we just return true for it return True return True
def docker_kubernetes_installed(verbose=False): """Check whether Docker AND Kubernetes have been installed. Args: verbose: Verbose or not. Returns: If Docker and Kubernetes have been both installed, return True, else False. """ if not docker_installed(verbose=verbose): color_print.error( 'it seems docker is not installed or correctly configured') color_print.error( 'you can run `metarget gadget install docker --version 18.03.1` to install one') return False if not kubernetes_installed(verbose=verbose): color_print.error( 'it seems kubernetes is not installed or correctly configured') color_print.error( 'you can run `metarget gadget install k8s --version 1.16.5` to install one') return False return True
def install_by_version(cls, gadgets, kata_runtime_type, http_proxy=None, https_proxy=None, no_proxy=None, verbose=False): """Install Kata-containers with specified version. Args: gadgets: Kata-containers gadgets (e.g. kata-containers). kata_runtime_type: Runtime of Kata (e.g. qemu/clh/...). http_proxy: HTTP proxy. https_proxy: HTTPS proxy. no_proxy: Domains which should be visited without proxy. verbose: Verbose or not. Returns: Boolean indicating whether Kata-containers is successfully installed or not. """ stdout, stderr = verbose_func.verbose_output(verbose) kata_static_tar_file = config.kata_static_tar_file % gadgets[0]['version'] kata_static_save_path = config.runtime_data_dir + kata_static_tar_file kata_static_tar = Path(kata_static_save_path) # 1. download kata tar if necessary if not kata_static_tar.exists(): color_print.debug( '{kata_tar} is going to be downloaded'.format( kata_tar=kata_static_tar_file)) kata_static_url = ( config.kata_static_url_prefix % gadgets[0]['version']) + kata_static_tar_file proxies = { 'http': http_proxy, 'https': https_proxy, 'no_proxy': no_proxy, } cls.download_file( url=kata_static_url, save_path=kata_static_save_path, proxies=proxies) else: color_print.debug( '{kata_tar} has been downloaded'.format( kata_tar=kata_static_tar_file)) # 2. decompress color_print.debug( 'decompressing files into {dest}'.format( dest=config.kata_tar_decompress_dest)) rmtree(path=config.kata_tar_decompress_dest, ignore_errors=True) system_func.mkdir_if_not_exist(config.kata_tar_decompress_dest) # use --strip-components=3 because `opt/kata/` path from tar are not needed # also, we should not just decompress files into `/` root path # which might cause risks temp_cmd = 'tar xf {file} -C {dest} --strip-components=3'.format( file=kata_static_save_path, dest=config.kata_tar_decompress_dest) try: subprocess.run( temp_cmd.split(), stdout=stdout, stderr=stderr, check=True) except subprocess.CalledProcessError: color_print.error( 'failed to decompress {kata_tar}'.format( kata_tar=kata_static_tar_file)) return False # 3. copy files color_print.debug( 'copying files to {kata_config_dir}'.format( kata_config_dir=config.kata_config_dir)) rmtree(path=config.kata_config_dir, ignore_errors=True) system_func.mkdir_if_not_exist(config.kata_config_dir) for file in glob.glob( config.kata_tar_decompress_dest + 'share/defaults/kata-containers/*'): file_copy( src=file, dst=config.kata_config_dir, follow_symlinks=False) # 4. configure runtime type color_print.debug( 'configuring kata runtime (type: {runtime_type})'.format( runtime_type=kata_runtime_type)) kata_configuration_file = Path( '{kata_config_dir}/configuration.toml'.format(kata_config_dir=config.kata_config_dir)) if kata_configuration_file.exists(): kata_configuration_file.unlink() kata_configuration_file.symlink_to( '{kata_config_dir}/configuration-{runtime_type}.toml'.format( kata_config_dir=config.kata_config_dir, runtime_type=kata_runtime_type)) # [5]. if docker is installed, # modify docker's configuration and restart docker # currently, metarget only supports docker # in the future more CRIs will be supported # see # https://github.com/kata-containers/documentation/blob/master/how-to/run-kata-with-k8s.md color_print.debug('configuring docker with kata-containers') if not cls._configure_docker_with_kata( base_dir=config.kata_tar_decompress_dest): color_print.error( 'failed to configure docker with kata-containers') return False return cls.reload_and_restart_docker(verbose=verbose)
def install(args): """Install a cloud native vulnerability. Install a vulnerable cloud native gadget with one vulnerability specified by args.cnv. Args: args.cnv: Name of the specified cloud native vulnerability. args.verbose: Verbose or not. args.http_proxy: HTTP proxy. args.https_proxy: HTTPS proxy. args.no_proxy: Domains which should be visited without proxy. Args below only used when installing vulnerability related to Kubernetes: args.cni_plugin: Name of CNI plugin. args.pod_network_cidr: CIDR of pod network. args.domestic: Pull Kubernetes images from domestic source or not. args.taint_master: Taint the master node or not. Returns: None. """ vulns = vuln_loader.load_vulns_by_dir(config.vuln_cn_dir_wildcard) vuln = filters.filter_vuln_by_name(vulns=vulns, name=args.cnv) if not vuln: color_print.error_and_exit( 'no cloud native vulnerability named {cnv}'.format(cnv=args.cnv)) # deploy vulnerability if vuln['class'] == 'config' or vuln['class'] == 'mount' or vuln[ 'class'] == 'no-vuln': if not checkers.docker_kubernetes_installed( verbose=args.verbose): # should install docker or k8s firstly return internal_cmds.deploy_vuln_resources_in_k8s(vuln, verbose=args.verbose) if vuln['class'].startswith('docker'): installed_flag = True # add a flag because more than one gadgets may be checked if checkers.docker_specified_installed(vuln['dependencies'], verbose=args.verbose): if checkers.gadget_in_gadgets(vuln['dependencies'], name='containerd', verbose=args.verbose): if not checkers.containerd_specified_installed( vuln['dependencies'], verbose=args.verbose): installed_flag = False if installed_flag: color_print.debug( '{vuln} already installed'.format(vuln=vuln['name'])) return color_print.debug( '{vuln} is going to be installed'.format(vuln=vuln['name'])) color_print.debug('uninstalling current docker gadgets if applicable') DockerInstaller.uninstall(verbose=args.verbose) if not DockerInstaller.install_by_version(vuln['dependencies'], verbose=args.verbose): color_print.error('failed to install {v}'.format(v=vuln['name'])) else: color_print.debug( '{v} successfully installed'.format(v=vuln['name'])) if vuln['class'] == 'kubernetes': if checkers.kubernetes_specified_installed(vuln['dependencies'], verbose=args.verbose): color_print.debug( '{vuln} already installed'.format(vuln=vuln['name'])) return if not checkers.docker_installed(verbose=args.verbose): color_print.error( 'it seems docker is not installed or correctly configured') color_print.error_and_exit( 'you can run `metarget gadget install docker --version 18.03.1` to install one' ) color_print.debug( '{vuln} is going to be installed'.format(vuln=vuln['name'])) color_print.debug('uninstalling current kubernetes if applicable') KubernetesInstaller.uninstall(verbose=args.verbose) temp_pod_network_cidr = args.pod_network_cidr if args.pod_network_cidr else config.cni_plugin_cidrs[ args.cni_plugin] if not KubernetesInstaller.install_by_version( vuln['dependencies'], cni_plugin=args.cni_plugin, pod_network_cidr=temp_pod_network_cidr, domestic=args.domestic, taint_master=args.taint_master, http_proxy=args.http_proxy, https_proxy=args.https_proxy, no_proxy=args.no_proxy, verbose=args.verbose): color_print.error('failed to install {v}'.format(v=vuln['name'])) else: color_print.debug( '{v} successfully installed'.format(v=vuln['name'])) if vuln['class'] == 'kernel': if checkers.kernel_specified_installed(vuln['dependencies'], verbose=args.verbose): color_print.debug( '{vuln} already installed'.format(vuln=vuln['name'])) return color_print.debug( '{vuln} is going to be installed'.format(vuln=vuln['name'])) if not KernelInstaller.install_by_version(gadgets=vuln['dependencies'], verbose=args.verbose): color_print.error('failed to install {v}'.format(v=vuln['name'])) else: color_print.debug( '{v} successfully installed'.format(v=vuln['name'])) # reboot reboot = color_print.debug_input('reboot system now? (y/n) ') if reboot == 'y' or reboot == 'Y': system_func.reboot_system(verbose=args.verbose) if vuln['class'] == 'kata-containers': if checkers.kata_specified_installed( temp_gadget=vuln['dependencies'], kata_runtime_type=vuln['annotations']['kata-runtime-type'], verbose=args.verbose): color_print.debug( '{vuln} already installed'.format(vuln=vuln['name'])) return if not checkers.docker_installed(verbose=args.verbose): color_print.error( 'it seems docker is not installed or correctly configured') color_print.error_and_exit( 'you can run `metarget gadget install docker --version 18.03.1` to install one' ) color_print.debug( '{vuln} is going to be installed'.format(vuln=vuln['name'])) color_print.debug('uninstalling current kata-containers if applicable') KataContainersInstaller.uninstall(verbose=args.verbose) if not KataContainersInstaller.install_by_version( gadgets=vuln['dependencies'], kata_runtime_type=vuln['annotations']['kata-runtime-type'], http_proxy=args.http_proxy, https_proxy=args.https_proxy, no_proxy=args.no_proxy, verbose=args.verbose): color_print.error('failed to install {v}'.format(v=vuln['name'])) else: color_print.debug( '{v} successfully installed'.format(v=vuln['name']))
def install(args): """Install a cloud native gadget with specified version. Args: args.gadget: Name of the specified cloud native gadget. args.verbose: Verbose or not. args.http_proxy: HTTP proxy. args.https_proxy: HTTPS proxy. args.no_proxy: Domains which should be visited without proxy. Args below only used when installing Kubernetes: args.cni_plugin: Name of CNI plugin. args.pod_network_cidr: CIDR of pod network. args.domestic: Pull Kubernetes images from domestic source or not. args.taint_master: Taint the master node or not. Args below only used when installing kata-containers: args.kata_runtime_type: Runtime of Kata (e.g. qemu/clh/...). Returns: None. """ if args.gadget == 'docker': temp_gadgets = [{'name': 'docker-ce', 'version': args.version}] if checkers.docker_specified_installed(temp_gadgets): color_print.debug( '{gadget} with version {version} already installed'.format( gadget=args.gadget, version=args.version)) return color_print.debug('uninstalling current docker if applicable') DockerInstaller.uninstall(verbose=args.verbose) if not DockerInstaller.install_by_version(temp_gadgets, verbose=args.verbose): color_print.error( 'failed to install {gadget}'.format(gadget=args.gadget)) else: color_print.debug( '{gadget} with version {version} successfully installed'. format(gadget=args.gadget, version=args.version)) if args.gadget == 'k8s': temp_gadgets = [ { 'name': 'kubelet', 'version': args.version }, { 'name': 'kubeadm', 'version': args.version }, { 'name': 'kubectl', 'version': args.version }, ] if checkers.kubernetes_specified_installed(temp_gadgets, verbose=args.verbose): color_print.debug( '{gadget} with version {version} already installed'.format( gadget=args.gadget, version=args.version)) return if not checkers.docker_installed(verbose=args.verbose): color_print.error( 'it seems docker is not installed or correctly configured') color_print.error_and_exit( 'you can run `metarget gadget install docker --version 18.03.1` to install one' ) color_print.debug('uninstalling current kubernetes if applicable') KubernetesInstaller.uninstall(verbose=args.verbose) temp_pod_network_cidr = args.pod_network_cidr if args.pod_network_cidr else config.cni_plugin_cidrs[ args.cni_plugin] if not KubernetesInstaller.install_by_version( temp_gadgets, cni_plugin=args.cni_plugin, pod_network_cidr=temp_pod_network_cidr, domestic=args.domestic, taint_master=args.taint_master, http_proxy=args.http_proxy, https_proxy=args.https_proxy, no_proxy=args.no_proxy, verbose=args.verbose): color_print.error( 'failed to install {gadget}'.format(gadget=args.gadget)) else: color_print.debug( '{gadget} successfully installed'.format(gadget=args.gadget)) if args.gadget == 'kata': temp_gadgets = [ { 'name': 'kata-containers', 'version': args.version }, ] if checkers.kata_specified_installed( temp_gadgets, kata_runtime_type=args.kata_runtime_type, verbose=args.verbose): color_print.debug( '{gadget} with version {version} already installed'.format( gadget=args.gadget, version=args.version)) return if not checkers.docker_installed(verbose=args.verbose): color_print.error( 'it seems docker is not installed or correctly configured') color_print.error_and_exit( 'you can run `metarget gadget install docker --version 18.03.1` to install one' ) color_print.debug('uninstalling current kata-containers if applicable') KataContainersInstaller.uninstall(verbose=args.verbose) if not KataContainersInstaller.install_by_version( temp_gadgets, kata_runtime_type=args.kata_runtime_type, verbose=args.verbose): color_print.error( 'failed to install {gadget}'.format(gadget=args.gadget)) else: color_print.debug( '{gadget} with version {version} (runtime type: {runtime_type}) successfully installed' .format(gadget=args.gadget, version=args.version, runtime_type=args.kata_runtime_type)) if args.gadget == 'kernel': temp_gadgets = [ { 'name': 'kernel', 'version': args.version }, ] if checkers.kernel_specified_installed(temp_gadgets, verbose=args.verbose): color_print.debug( '{gadget} with version {version} already installed'.format( gadget=args.gadget, version=args.version)) return if not KernelInstaller.install_by_version(temp_gadgets, verbose=args.verbose): color_print.error( 'failed to install {gadget}'.format(gadget=args.gadget)) else: color_print.debug( '{gadget} successfully installed'.format(gadget=args.gadget)) # reboot reboot = color_print.debug_input('reboot system now? (y/n) ') if reboot == 'y' or reboot == 'Y': system_func.reboot_system(verbose=args.verbose)