def _run(self, args: argparse.Namespace) -> int: """Execute and process the args.""" try: log.set_log_level_from_args(args) return self.replicate_object(args.model, args) except Exception as e: # pragma: no cover return handle_generic_command_exception(e, logger, 'Error while replicating model')
def assemble_model(cls, model_alias: str, args: argparse.Namespace) -> int: """Assemble a top level OSCAL model within the trestle dist directory.""" log.set_log_level_from_args(args) logger.info(f'Assembling models of type {model_alias}.') trestle_root = args.trestle_root # trestle root is set via command line in args. Default is cwd. if not trestle_root or not file_utils.is_valid_project_root( args.trestle_root): raise TrestleRootError( f'Given directory {trestle_root} is not a trestle project.') model_names = [] if args.name: model_names = [args.name] logger.info( f'Assembling single model of type {model_alias}: {args.name}.') else: model_names = ModelUtils.get_models_of_type( model_alias, trestle_root) nmodels = len(model_names) logger.info( f'Assembling {nmodels} found models of type {model_alias}.') if len(model_names) == 0: logger.info(f'No models found to assemble of type {model_alias}.') return CmdReturnCodes.SUCCESS.value for model_name in model_names: # contruct path to the model file name root_model_dir = trestle_root / ModelUtils.model_type_to_model_dir( model_alias) model_file_type = file_utils.get_contextual_file_type( root_model_dir / model_name) model_file_name = f'{model_alias}{FileContentType.to_file_extension(model_file_type)}' root_model_filepath = root_model_dir / model_name / model_file_name if not root_model_filepath.exists(): raise TrestleError( f'No top level model file at {root_model_dir}') # distributed load _, _, assembled_model = ModelUtils.load_distributed( root_model_filepath, args.trestle_root) plural_alias = ModelUtils.model_type_to_model_dir(model_alias) assembled_model_dir = trestle_root / const.TRESTLE_DIST_DIR / plural_alias assembled_model_filepath = assembled_model_dir / f'{model_name}.{args.extension}' plan = Plan() plan.add_action(CreatePathAction(assembled_model_filepath, True)) plan.add_action( WriteFileAction( assembled_model_filepath, Element(assembled_model), FileContentType.to_content_type(f'.{args.extension}'))) plan.execute() return CmdReturnCodes.SUCCESS.value
def _run(self, args: argparse.Namespace) -> int: try: log.set_log_level_from_args(args) mode_args = argparse.Namespace(mode=VAL_MODE_ALL) validator = vfact.validator_factory.get(mode_args) return validator.validate(args) except Exception as e: # pragma: no cover return handle_generic_command_exception(e, logger, 'Error while validating contents of a trestle model')
def _run(self, args: argparse.Namespace) -> int: try: log.set_log_level_from_args(args) trestle_root = pathlib.Path(args.trestle_root) return self.filter_ssp(trestle_root, args.name, args.profile, args.output, args.regenerate, args.version) except Exception as e: # pragma: no cover return handle_generic_command_exception( e, logger, 'Error generating the filtered ssp')
def _run(self, args: argparse.Namespace) -> int: try: log.set_log_level_from_args(args) file_path: pathlib.Path = args.file.resolve() if not file_path.exists() or not file_path.is_file(): raise TrestleError( 'File path provided does not exist or is a directory') element_str: str = args.element if ',' in element_str: logger.warning('Only a single element path is allowed.') return self.partial_object_validate(file_path, element_str) except Exception as e: # pragma: no cover return handle_generic_command_exception( e, logger, 'Error while validating OSCAL file')
def _run(self, args: argparse.Namespace) -> int: """Remove an OSCAL component/subcomponent to the specified component. This method takes input a filename and a list of comma-seperated element path. Element paths are field aliases. The method first finds the parent model from the file and loads the file into the model. Then the method executes 'remove' for each of the element paths specified. """ try: log.set_log_level_from_args(args) args_dict = args.__dict__ file_path = pathlib.Path(args_dict[const.ARG_FILE]).resolve() relative_path = file_path.relative_to(args.trestle_root) # Get parent model and then load json into parent model parent_model, parent_alias = ModelUtils.get_relative_model_type( relative_path) parent_object = parent_model.oscal_read(file_path) parent_element = Element(parent_object, parent_alias) add_plan = Plan() # Do _remove for each element_path specified in args element_paths: List[str] = str( args_dict[const.ARG_ELEMENT]).split(',') for elm_path_str in element_paths: element_path = ElementPath(elm_path_str) remove_action, parent_element = self.remove( element_path, parent_element) add_plan.add_action(remove_action) create_action = CreatePathAction(file_path, True) write_action = WriteFileAction( file_path, parent_element, FileContentType.to_content_type(file_path.suffix)) add_plan.add_action(remove_action) add_plan.add_action(create_action) add_plan.add_action(write_action) add_plan.execute() return CmdReturnCodes.SUCCESS.value except Exception as e: return err.handle_generic_command_exception( e, logger, 'Error while removing OSCAL component')
def _run(self, args: argparse.Namespace) -> int: try: logger.debug('Entering trestle href.') log.set_log_level_from_args(args) profile_name: str = args.name new_href: str = args.href.strip("'") item_num = args.item return self.change_import_href(args.trestle_root, profile_name, new_href, item_num) except Exception as e: # pragma: no cover return handle_generic_command_exception( e, logger, f'Error while changing href or import in profile: {e}')
def _run(self, args: argparse.Namespace) -> int: try: logger.debug('Entering trestle describe.') log.set_log_level_from_args(args) if args.file: model_file = pathlib.Path(args.file) element = '' if not args.element else args.element.strip("'") results = self.describe(model_file.resolve(), element, args.trestle_root) return CmdReturnCodes.SUCCESS.value if len(results) > 0 else CmdReturnCodes.COMMAND_ERROR.value else: raise TrestleIncorrectArgsError('No file specified for command describe.') except Exception as e: # pragma: no cover return handle_generic_command_exception(e, logger, 'Error while describing contents of a model')
def create_object(cls, model_alias: str, object_type: Type[TopLevelOscalModel], args: argparse.Namespace) -> int: """Create a top level OSCAL object within the trestle directory, leveraging functionality in add.""" log.set_log_level_from_args(args) trestle_root = args.trestle_root # trestle root is set via command line in args. Default is cwd. if not trestle_root or not file_utils.is_valid_project_root( args.trestle_root): raise err.TrestleRootError( f'Given directory {trestle_root} is not a trestle project.') plural_path = ModelUtils.model_type_to_model_dir(model_alias) desired_model_dir = trestle_root / plural_path / args.output desired_model_path = desired_model_dir / (model_alias + '.' + args.extension) if desired_model_path.exists(): raise err.TrestleError( f'OSCAL file to be created here: {desired_model_path} exists.') # Create sample model. sample_model = generators.generate_sample_model( object_type, include_optional=args.include_optional_fields) # Presuming top level level model not sure how to do the typing for this. sample_model.metadata.title = f'Generic {model_alias} created by trestle named {args.output}.' # type: ignore sample_model.metadata.last_modified = datetime.now().astimezone() sample_model.metadata.oscal_version = trestle.oscal.OSCAL_VERSION sample_model.metadata.version = '0.0.0' top_element = Element(sample_model, model_alias) create_action = CreatePathAction(desired_model_path.resolve(), True) write_action = WriteFileAction( desired_model_path.resolve(), top_element, FileContentType.to_content_type(desired_model_path.suffix)) # create a plan to write the directory and file. create_plan = Plan() create_plan.add_action(create_action) create_plan.add_action(write_action) create_plan.execute() return CmdReturnCodes.SUCCESS.value
def _run(self, args: argparse.Namespace) -> int: try: log.set_log_level_from_args(args) trestle_root = pathlib.Path(args.trestle_root) # the original profile model name defaults to being the same as the new one prof_name = args.output if not args.name else args.name return self.assemble_profile( trestle_root=trestle_root, orig_profile_name=prof_name, md_name=args.markdown, new_profile_name=args.output, set_parameters=args.set_parameters, regenerate=args.regenerate, version=args.version, required_sections=args.required_sections, allowed_sections=args.allowed_sections) except Exception as e: # pragma: no cover return handle_generic_command_exception( e, logger, 'Assembly of markdown to profile failed')
def _run(self, args: argparse.Namespace) -> int: """Split an OSCAL file into elements.""" try: log.set_log_level_from_args(args) trace.log('Entering trestle split.') # get the Model args_raw: Dict[str, str] = args.__dict__ # remove any quotes passed in as on windows platforms elements_clean: str = args_raw[const.ARG_ELEMENT].strip("'") file_name = '' file_name = '' if not args_raw[const.ARG_FILE] else args_raw[const.ARG_FILE] # cwd must be in the model directory if file to split is not specified effective_cwd = pathlib.Path.cwd() return self.perform_split(effective_cwd, file_name, elements_clean, args.trestle_root) except Exception as e: # pragma: no cover return handle_generic_command_exception(e, logger, 'Error while performing a split operation')
def _initialize(self, args: argparse.Namespace) -> int: log.set_log_level_from_args(args) # Externalize self.trestle_root = args.trestle_root self.task_name = args.task_name try: self.global_ = args.__getattribute__('global') except AttributeError: self.global_ = None if self.task_name: self.task_path = self.trestle_root / self.task_name if not file_utils.is_directory_name_allowed(self.task_name): logger.error( f'Task name {self.task_name} is invalid as it interferes with OSCAL and trestle reserved names.' ) return CmdReturnCodes.COMMAND_ERROR.value rc = self._setup_template_dir(args) return rc
def _run(self, args: argparse.Namespace) -> int: try: log.set_log_level_from_args(args) trestle_root: pathlib.Path = args.trestle_root if not file_utils.is_directory_name_allowed(args.output): raise TrestleError( f'{args.output} is not an allowed directory name') yaml_header: dict = {} if args.yaml_header: try: logging.debug( f'Loading yaml header file {args.yaml_header}') yaml = YAML() yaml_header = yaml.load( pathlib.Path(args.yaml_header).open('r')) except YAMLError as e: raise TrestleError( f'YAML error loading yaml header for ssp generation: {e}' ) # combine command line sections with any in the yaml header, with priority to command line sections_dict: Optional[Dict[str, str]] = None if args.sections: sections_dict = sections_to_dict(args.sections) profile_path = trestle_root / f'profiles/{args.name}/profile.json' markdown_path = trestle_root / args.output return self.generate_markdown(trestle_root, profile_path, markdown_path, yaml_header, args.overwrite_header_values, sections_dict, args.required_sections) except Exception as e: # pragma: no cover return handle_generic_command_exception( e, logger, 'Generation of the profile markdown failed')
def _run(self, args: argparse.Namespace) -> int: """Create a trestle project in the current directory.""" try: log.set_log_level_from_args(args) dir_path: pathlib.Path = args.trestle_root if not dir_path.exists() or not dir_path.is_dir(): raise TrestleRootError( f'Initialization failed. Given directory {dir_path} does not exist or is not a directory.' ) # Create directories self._create_directories(dir_path) # Create config file self._copy_config_file(dir_path) logger.info( f'Initialized trestle project successfully in {dir_path}') return CmdReturnCodes.SUCCESS.value except Exception as e: # pragma: no cover return handle_generic_command_exception( e, logger, 'Failed to initialize Trestle working directory.')
def _run(self, args: argparse.Namespace): try: log.set_log_level_from_args(args) logger.debug(f'Starting {self.name} command') input_path = pathlib.Path(args.input) output_path = pathlib.Path(args.output) if args.look_up_table: lut_table = pathlib.Path(args.look_up_table) lookup_table_path = pathlib.Path.cwd() / lut_table lut = JinjaCmd.load_LUT(lookup_table_path, args.external_lut_prefix) status = JinjaCmd.jinja_ify( pathlib.Path(args.trestle_root), input_path, output_path, args.system_security_plan, args.profile, lut, number_captions=args.number_captions, parameters_formatting=args.param_formatting ) else: status = JinjaCmd.jinja_ify( pathlib.Path(args.trestle_root), input_path, output_path, args.system_security_plan, args.profile, number_captions=args.number_captions, parameters_formatting=args.param_formatting ) logger.debug(f'Done {self.name} command') return status except Exception as e: # pragma: no cover return handle_generic_command_exception(e, logger, 'Error while generating markdown via Jinja template')
def _run(self, args: argparse.Namespace) -> int: try: logger.debug('Entering trestle task.') log.set_log_level_from_args(args) # Initial logic for conflicting args if args.task and args.list: raise TrestleIncorrectArgsError( 'Task name or -l can be provided not both.') if not args.task and not args.list: raise TrestleIncorrectArgsError( 'Either a trestle task or "-l/--list" shoudl be passed as input arguments.' ) # Ensure trestle directory (must be true) trestle_root = args.trestle_root # trestle root is set via command line in args. Default is cwd. if not trestle_root or not file_utils.is_valid_project_root( args.trestle_root): raise TrestleError( f'Given directory: {trestle_root} is not a trestle project.' ) config_path = trestle_root / const.TRESTLE_CONFIG_DIR / const.TRESTLE_CONFIG_FILE if args.config: config_path = pathlib.Path(args.config) if not config_path.exists(): raise TrestleError( f'Config file at {config_path} does not exist.') # permit ${name} in config definitions global_config = configparser.ConfigParser( interpolation=configparser.ExtendedInterpolation()) global_config.read_file( config_path.open('r', encoding=const.FILE_ENCODING)) # run setup task_index = self._build_task_index() # Clean to run if args.list: self._list_tasks(task_index) return CmdReturnCodes.SUCCESS.value # run the task if args.task not in task_index.keys(): raise TrestleIncorrectArgsError( f'Unknown trestle task: {args.task}') logger.debug(f'Loading task: {args.task}') section_label = 'task.' + args.task config_section: Optional[configparser.SectionProxy] = None if section_label in global_config.sections(): config_section = global_config[section_label] else: logger.warning( f'Config file was not configured with the appropriate section for the task: "[{section_label}]"' ) task = task_index[args.task](config_section) if args.info: task.print_info() return CmdReturnCodes.SUCCESS.value simulate_result = task.simulate() if not (simulate_result == TaskOutcome.SIM_SUCCESS): raise TrestleError( f'Task {args.task} reported a {simulate_result}') actual_result = task.execute() if not (actual_result == TaskOutcome.SUCCESS): raise TrestleError( f'Task {args.task} reported a {actual_result}') logger.info(f'Task: {args.task} executed successfully.') return CmdReturnCodes.SUCCESS.value except Exception as e: # pragma: no cover return handle_generic_command_exception( e, logger, 'Error while executing Trestle task')
def _run(self, args: argparse.Namespace) -> int: try: log.set_log_level_from_args(args) trestle_root = args.trestle_root if not file_utils.is_directory_name_allowed(args.output): raise TrestleError( f'{args.output} is not an allowed directory name') profile_path = trestle_root / f'profiles/{args.profile}/profile.json' yaml_header: dict = {} if args.yaml_header: try: logging.debug( f'Loading yaml header file {args.yaml_header}') yaml = YAML() yaml_header = yaml.load( pathlib.Path(args.yaml_header).open('r')) except YAMLError as e: raise TrestleError( f'YAML error loading yaml header for ssp generation: {e}' ) markdown_path = trestle_root / args.output profile_resolver = ProfileResolver() resolved_catalog = profile_resolver.get_resolved_profile_catalog( trestle_root, profile_path) catalog_interface = CatalogInterface(resolved_catalog) sections_dict: Dict[str, str] = {} if args.sections: sections_dict = sections_to_dict(args.sections) if 'statement' in sections_dict: raise TrestleError( 'Statement is not allowed as a section name.') # add any existing sections from the controls but only have short names control_section_short_names = catalog_interface.get_sections() for short_name in control_section_short_names: if short_name not in sections_dict: sections_dict[short_name] = short_name logger.debug(f'ssp sections dict: {sections_dict}') catalog_interface.write_catalog_as_markdown( md_path=markdown_path, yaml_header=yaml_header, sections_dict=sections_dict, prompt_responses=True, additional_content=False, profile=None, overwrite_header_values=args.overwrite_header_values, set_parameters=False, required_sections=None, allowed_sections=args.allowed_sections) return CmdReturnCodes.SUCCESS.value except Exception as e: # pragma: no cover return handle_generic_command_exception( e, logger, 'Error while writing markdown from catalog')
def _run(self, args: argparse.Namespace) -> int: try: log.set_log_level_from_args(args) trestle_root = pathlib.Path(args.trestle_root) md_path = trestle_root / args.markdown # the original, reference ssp name defaults to same as output if name not specified # thus in cyclic editing you are reading and writing same json ssp orig_ssp_name = args.output if args.name: orig_ssp_name = args.name new_ssp_name = args.output # if orig ssp exists - need to load it rather than instantiate new one orig_ssp_path = ModelUtils.path_for_top_level_model( trestle_root, orig_ssp_name, ossp.SystemSecurityPlan, FileContentType.JSON) # if output ssp already exists, load it to see if new one is different existing_ssp: Optional[ossp.SystemSecurityPlan] = None new_ssp_path = ModelUtils.path_for_top_level_model( trestle_root, new_ssp_name, ossp.SystemSecurityPlan, FileContentType.JSON) if new_ssp_path.exists(): _, _, existing_ssp = ModelUtils.load_distributed( new_ssp_path, trestle_root) ssp: ossp.SystemSecurityPlan comp_dict: Dict[str, ossp.SystemComponent] = {} # need to load imp_reqs from markdown but need component first if orig_ssp_path.exists(): # load the existing json ssp _, _, ssp = ModelUtils.load_distributed( orig_ssp_path, trestle_root) for component in ssp.system_implementation.components: comp_dict[component.title] = component # read the new imp reqs from markdown and have them reference existing components imp_reqs = CatalogInterface.read_catalog_imp_reqs( md_path, comp_dict) self._merge_imp_reqs(ssp, imp_reqs) else: # create a sample ssp to hold all the parts ssp = gens.generate_sample_model(ossp.SystemSecurityPlan) # load the imp_reqs from markdown and create components as needed, referenced by ### headers imp_reqs = CatalogInterface.read_catalog_imp_reqs( md_path, comp_dict) # create system implementation system_imp: ossp.SystemImplementation = gens.generate_sample_model( ossp.SystemImplementation) ssp.system_implementation = system_imp # create a control implementation to hold the implementated requirements control_imp: ossp.ControlImplementation = gens.generate_sample_model( ossp.ControlImplementation) control_imp.implemented_requirements = imp_reqs control_imp.description = const.SSP_SYSTEM_CONTROL_IMPLEMENTATION_TEXT # insert the parts into the ssp ssp.control_implementation = control_imp ssp.system_implementation = system_imp # we don't have access to the original profile so we don't know the href import_profile: ossp.ImportProfile = gens.generate_sample_model( ossp.ImportProfile) import_profile.href = 'REPLACE_ME' ssp.import_profile = import_profile # now that we know the complete list of needed components, add them to the sys_imp # TODO if the ssp already existed then components may need to be removed if not ref'd by imp_reqs component_list: List[ossp.SystemComponent] = [] for comp in comp_dict.values(): component_list.append(comp) if ssp.system_implementation.components: # reconstruct list with same order as existing, but add/remove components as needed new_list: List[ossp.SystemComponent] = [] for comp in ssp.system_implementation.components: if comp in component_list: new_list.append(comp) for comp in component_list: if comp not in new_list: new_list.append(comp) ssp.system_implementation.components = new_list elif component_list: ssp.system_implementation.components = component_list self._generate_roles_in_metadata(ssp) if args.version: ssp.metadata.version = com.Version(__root__=args.version) if existing_ssp == ssp: logger.info( 'No changes to assembled ssp so ssp not written out.') return CmdReturnCodes.SUCCESS.value if args.regenerate: ssp, _, _ = ModelUtils.regenerate_uuids(ssp) ModelUtils.update_last_modified(ssp) # write out the ssp as json ModelUtils.save_top_level_model(ssp, trestle_root, new_ssp_name, FileContentType.JSON) return CmdReturnCodes.SUCCESS.value except Exception as e: # pragma: no cover return handle_generic_command_exception( e, logger, 'Error while assembling SSP')
def _run(self, args: argparse.Namespace) -> int: """Top level import run command.""" try: log.set_log_level_from_args(args) trestle_root = args.trestle_root if not file_utils.is_valid_project_root(trestle_root): raise TrestleRootError( f'Attempt to import from non-valid trestle project root {trestle_root}' ) input_uri = args.file if cache.FetcherFactory.in_trestle_directory( trestle_root, input_uri): raise TrestleError( f'Imported file {input_uri} cannot be from current trestle project. Use duplicate instead.' ) content_type = FileContentType.to_content_type( '.' + input_uri.split('.')[-1]) fetcher = cache.FetcherFactory.get_fetcher(trestle_root, str(input_uri)) model_read, parent_alias = fetcher.get_oscal(True) plural_path = ModelUtils.model_type_to_model_dir(parent_alias) output_name = args.output desired_model_dir = trestle_root / plural_path desired_model_path: pathlib.Path = desired_model_dir / output_name / parent_alias desired_model_path = desired_model_path.with_suffix( FileContentType.to_file_extension(content_type)).resolve() if desired_model_path.exists(): raise TrestleError( f'Cannot import because file to be imported here: {desired_model_path} already exists.' ) if args.regenerate: logger.debug( f'regenerating uuids in imported file {input_uri}') model_read, lut, nchanged = ModelUtils.regenerate_uuids( model_read) logger.debug( f'uuid lut has {len(lut.items())} entries and {nchanged} refs were updated' ) top_element = Element(model_read) create_action = CreatePathAction(desired_model_path, True) write_action = WriteFileAction(desired_model_path, top_element, content_type) # create a plan to create the directory and write the imported file. import_plan = Plan() import_plan.add_action(create_action) import_plan.add_action(write_action) import_plan.execute() args = argparse.Namespace(file=desired_model_path, verbose=args.verbose, trestle_root=args.trestle_root, type=None, all=None) rollback = False try: rc = validatecmd.ValidateCmd()._run(args) if rc > 0: logger.warning( f'Validation of imported file {desired_model_path} did not pass' ) rollback = True except TrestleError as err: logger.warning( f'Import of {str(input_uri)} failed with validation error: {err}' ) rollback = True if rollback: logger.debug( f'Rolling back import of {str(input_uri)} to {desired_model_path}' ) try: import_plan.rollback() except TrestleError as err: raise TrestleError( f'Import failed in plan rollback: {err}. Manually remove {desired_model_path} to recover.' ) logger.debug( f'Successful rollback of import to {desired_model_path}') return CmdReturnCodes.COMMAND_ERROR.value return CmdReturnCodes.SUCCESS.value except Exception as e: # pragma: no cover return handle_generic_command_exception( e, logger, 'Error while importing OSCAL file')