ssp_io
trestle.core.ssp_io
¤
Handle direct IO for writing SSP responses as markdown.
logger
¤
Classes¤
SSPMarkdownWriter
¤
Class to write control responses as markdown.
Functions in this class are mainly used by jinja and not by the trestle code itself.
Source code in trestle/core/ssp_io.py
class SSPMarkdownWriter():
"""
Class to write control responses as markdown.
Functions in this class are mainly used by jinja and not by the trestle code itself.
"""
def __init__(self, trestle_root: pathlib.Path) -> None:
"""Initialize the class."""
self._trestle_root = trestle_root
self._ssp: ssp.SystemSecurityPlan = None
self._resolved_catalog: Catalog = None
self._catalog_interface: CatalogInterface = None
def set_ssp(self, ssp: ssp.SystemSecurityPlan) -> None:
"""Set ssp."""
self._ssp = ssp
def set_catalog(self, resolved_catalog: Catalog) -> None:
"""Set catalog."""
self._resolved_catalog = resolved_catalog
self._catalog_interface = catalog_interface.CatalogInterface(self._resolved_catalog)
def get_control_statement(self, control_id: str, level: int) -> str:
"""
Get the control statement for an ssp - to be printed in markdown as a structured list.
Args:
control_id: The control_id to use.
Returns:
A markdown blob as a string.
"""
if not self._resolved_catalog:
raise TrestleError('Cannot get control statement, set resolved catalog first.')
writer = DocsControlWriter()
control = self._catalog_interface.get_control(control_id)
if not control:
return ''
control_lines = writer.get_control_statement_ssp(control)
return self._build_tree_and_adjust(control_lines, level)
def get_control_part(self, control_id: str, part_name: str, level: int) -> str:
"""Get control part with given name."""
control_part = self._catalog_interface.get_control_part_prose(control_id, part_name)
md_list = self._write_str_with_header(
f'Control Part: {part_name} for control: {control_id}', control_part, level
)
return self._build_tree_and_adjust(md_list.split('\n'), level)
def get_fedramp_control_tables(self, control_id: str, level: int, label_column: bool = False) -> str:
"""Get the fedramp metadata as markdown tables, with optional third label column for params.
The fedramp metadata has the following elements:
- Responsible roles field
- Parameter values table
- Implementation status field
- Control origination field
Returns:
tables as one coherent markdown blob.
"""
resp_roles_table = self.get_responsible_roles_table(control_id, level)
params_values = self._parameter_table(control_id, level, label_column)
impl_status = self.get_fedramp_implementation_status(control_id, level)
control_orig = self.get_fedramp_control_origination(control_id, level)
final_output = ''
if resp_roles_table:
final_output += resp_roles_table
if params_values:
final_output += '\n' + params_values
if impl_status:
final_output += '\n' + impl_status
if control_orig:
final_output += '\n' + control_orig
return final_output
def get_responsible_roles_table(self, control_id: str, level: int) -> str:
"""
For each role id - if the role exists in metadata use the title as what gets printed in the roles table.
If not (for now) warn and use the role-id as the printed text.
"""
if self._ssp is None:
raise TrestleError('Cannot get responsible roles, SSP is not set.')
for impl_requirement in self._ssp.control_implementation.implemented_requirements:
if impl_requirement.control_id == control_id:
if impl_requirement.responsible_roles:
resp_roles = as_list(impl_requirement.responsible_roles)
role_ids = [role.role_id.replace('_', ' ') for role in resp_roles]
# now check if this role exists in the metadata
role_titles = dict(zip(role_ids, role_ids))
roles = as_list(self._ssp.metadata.roles)
for role in roles:
if role.id in role_ids:
role_titles[role.id] = role.title
# dictionary to md table
md_list = self._write_table_with_header(
'Responsible Roles.', [[key, role_titles[key]] for key in role_titles.keys()],
['Role ID', 'Title'],
level
)
return md_list
else:
logger.warning(
f'No responsible roles were found for the control with id: {control_id} in given SSP.'
)
return ''
return ''
def _parameter_table(self, control_id: str, level: int, label_column: bool = False) -> str:
"""Print Param_id | ValueOrLabelOrChoices | Optional Label Column."""
if not self._ssp:
raise TrestleError('Cannot get parameter table, set SSP first.')
writer = DocsControlWriter()
control = self._catalog_interface.get_control(control_id)
if not control:
return ''
params_lines = writer.get_param_table(control, label_column)
# need to make sure no params still have moustaches. convert to brackets to avoid jinja complaints
clean_lines = []
for line in params_lines:
clean_lines.append(line.replace('{{', '[[').replace('}}', ']]'))
tree = DocsMarkdownNode.build_tree_from_markdown(clean_lines)
tree.change_header_level_by(level)
return tree.content.raw_text
def get_fedramp_implementation_status(self, control_id: str, level: int) -> str:
"""
Print implementation status as a list of items, only showing those that are applicable for the control.
This is unlike the word document FedRAMP which uses checkboxes on standard set of options.
Using a LUT to map between structured data fields, defined by FedRAMP and historical text.
"""
if not self._ssp:
raise TrestleError('Cannot get Fedramp implementation status, set SSP first.')
implementation_statuses: List[str] = []
control_impl_req = self._control_implemented_req(control_id)
if control_impl_req and control_impl_req.props:
for prop in control_impl_req.props:
if prop.name == IMPLEMENTATION_STATUS:
implementation_statuses.append(prop.value)
md_list = self._write_list_with_header('FedRamp Implementation Status.', implementation_statuses, level)
return md_list
def get_fedramp_control_origination(self, control_id: str, level: int) -> str:
"""
Print control origination, as a list of items, only showing those that are applicable for the control.
Using a LUT to map between structured data fields, defined by FedRAMP and historical text.
"""
if not self._ssp:
raise TrestleError('Cannot get FedRamp control origination, set SSP first.')
control_origination = []
control_impl_req = self._control_implemented_req(control_id)
if control_impl_req and control_impl_req.props:
for prop in control_impl_req.props:
if prop.name == CONTROL_ORIGINATION:
control_origination.append(prop.value)
md_list = self._write_list_with_header('FedRamp Control Origination.', control_origination, level)
return md_list
@staticmethod
def _write_component_prompt(
md_writer: MDWriter,
comp_name: str,
prose: str,
rules: List[str],
status: str,
show_rules: bool,
show_status: bool
) -> None:
header = f'Component: {comp_name}'
md_writer.new_header(1, header)
md_writer.set_indent_level(-1)
md_writer.new_line(prose)
md_writer.set_indent_level(-1)
if rules and show_rules:
md_writer.new_header(2, title='Rules:')
md_writer.set_indent_level(-1)
md_writer.new_list(rules)
md_writer.set_indent_level(-1)
if status and show_status:
md_writer.new_header(2, title=f'Implementation Status: {status}')
def get_control_response(
self,
control_id: str,
level: int,
write_empty_responses: bool = False,
show_comp: bool = True,
show_rules: bool = False,
show_status: bool = True
) -> str:
"""
Get the full control implemented requirements, broken down based on the available control responses.
Args:
control_id: id of the control
level: level of indentation
write_empty_responses: write response even if empty
show_comp: show the component name in the response
Notes:
This is intended to be invoked from a jinja template that has already written out the prompt for
control response
"""
if not self._resolved_catalog:
raise TrestleError('Cannot get control response, set resolved catalog first.')
control = self._catalog_interface.get_control(control_id)
imp_req = self._control_implemented_req(control_id)
if not imp_req:
logger.info(f'No implemented requirements found for the control {control_id}')
return ''
md_writer = MDWriter(None)
system_prose = ''
system_rules = []
system_status = STATUS_OPERATIONAL
imp_req_responses = self._get_responses_by_components(imp_req, write_empty_responses)
if SSP_MAIN_COMP_NAME in imp_req_responses:
system_prose, system_rules, system_status = imp_req_responses[SSP_MAIN_COMP_NAME]
SSPMarkdownWriter._write_component_prompt(
md_writer, SSP_MAIN_COMP_NAME, system_prose, system_rules, system_status, show_rules, show_status
)
# if a control has no statement sub-parts then get the response bycomps from the imp_req itself
# otherwise get them from the statements in the imp_req
# an imp_req and a statement are both things that can have bycomps
has_bycomps = imp_req.statements if imp_req.statements else [imp_req]
for has_bycomp in has_bycomps:
statement_id = getattr(has_bycomp, 'statement_id', f'{control_id}_smt')
label = statement_id
part_name = None
# look up label for this statement
if control.parts:
found_label, part = self._catalog_interface.get_statement_label_if_exists(control_id, statement_id)
if found_label:
label = found_label
part_name = part.name
response_per_component = self._get_responses_by_components(has_bycomp, write_empty_responses)
if response_per_component or write_empty_responses:
if part_name and part_name == ITEM:
# print part header only if subitem
header = f'Implementation for part {label}'
md_writer.new_header(1, title=header)
for comp_name, comp_response in response_per_component.items():
if comp_name == SSP_MAIN_COMP_NAME:
continue
prose, rules, status = comp_response
if show_comp:
SSPMarkdownWriter._write_component_prompt(
md_writer, comp_name, prose, rules, status, show_rules, show_status
)
lines = md_writer.get_lines()
tree = DocsMarkdownNode.build_tree_from_markdown(lines)
tree.change_header_level_by(level)
return tree.content.raw_text
def _get_responses_by_components(self, has_bycomps: TypeWithByComps,
write_empty_responses: bool) -> Dict[str, Tuple[str, List[str], str]]:
"""Get response per component, substitute component id with title if possible."""
response_per_component: Dict[str, Tuple[str, str]] = {}
for by_comp in as_list(has_bycomps.by_components): # type: ignore
# look up component title
subheader = by_comp.component_uuid
prose = ''
status = ''
rules = []
if self._ssp.system_implementation.components:
for comp in self._ssp.system_implementation.components:
if comp.uuid == by_comp.component_uuid:
title = comp.title
if title:
subheader = title
if by_comp.description:
prose = by_comp.description
if by_comp.implementation_status:
status = by_comp.implementation_status.state
rules, _ = ControlInterface.get_rule_list_for_item(by_comp)
if prose or (not prose and write_empty_responses):
if subheader:
response_per_component[subheader] = (prose, rules, status)
return response_per_component
def _control_implemented_req(self, control_id: str) -> Optional[ssp.ImplementedRequirement]:
"""Retrieve control implemented requirement by control-id."""
requirements = self._ssp.control_implementation.implemented_requirements
for requirement in requirements:
if requirement.control_id == control_id:
return requirement
logger.debug(f'No implemented requirement found for control {control_id}')
return None
def _write_list_with_header(self, header: str, lines: List[str], level: int) -> str:
if lines:
md_writer = MDWriter(None)
md_writer.new_paragraph()
md_writer.new_header(level=1, title=header)
md_writer.set_indent_level(-1)
md_writer.new_list(lines)
md_writer.set_indent_level(-1)
return self._build_tree_and_adjust(md_writer.get_lines(), level)
return ''
def _write_table_with_header(
self, header: str, values: List[List[str]], table_header: List[str], level: int
) -> str:
if values and values[0]:
md_writer = MDWriter(None)
md_writer.new_paragraph()
md_writer.new_header(level=1, title=header)
md_writer.set_indent_level(-1)
md_writer.new_table(values, table_header)
md_writer.set_indent_level(-1)
return self._build_tree_and_adjust(md_writer.get_lines(), level)
return ''
def _write_str_with_header(self, header: str, text: str, level: int) -> str:
if text:
md_writer = MDWriter(None)
md_writer.new_paragraph()
md_writer.new_header(level=1, title=header)
md_writer.set_indent_level(-1)
md_writer.new_line(text)
md_writer.set_indent_level(-1)
return self._build_tree_and_adjust(md_writer.get_lines(), level)
return ''
def _build_tree_and_adjust(self, lines: List[str], level: int) -> str:
tree = DocsMarkdownNode.build_tree_from_markdown(lines)
tree.change_header_level_by(level)
return tree.content.raw_text
Methods¤
__init__(self, trestle_root)
special
¤
Initialize the class.
Source code in trestle/core/ssp_io.py
def __init__(self, trestle_root: pathlib.Path) -> None:
"""Initialize the class."""
self._trestle_root = trestle_root
self._ssp: ssp.SystemSecurityPlan = None
self._resolved_catalog: Catalog = None
self._catalog_interface: CatalogInterface = None
get_control_part(self, control_id, part_name, level)
¤
Get control part with given name.
Source code in trestle/core/ssp_io.py
def get_control_part(self, control_id: str, part_name: str, level: int) -> str:
"""Get control part with given name."""
control_part = self._catalog_interface.get_control_part_prose(control_id, part_name)
md_list = self._write_str_with_header(
f'Control Part: {part_name} for control: {control_id}', control_part, level
)
return self._build_tree_and_adjust(md_list.split('\n'), level)
get_control_response(self, control_id, level, write_empty_responses=False, show_comp=True, show_rules=False, show_status=True)
¤
Get the full control implemented requirements, broken down based on the available control responses.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
control_id |
str |
id of the control |
required |
level |
int |
level of indentation |
required |
write_empty_responses |
bool |
write response even if empty |
False |
show_comp |
bool |
show the component name in the response |
True |
Notes
This is intended to be invoked from a jinja template that has already written out the prompt for control response
Source code in trestle/core/ssp_io.py
def get_control_response(
self,
control_id: str,
level: int,
write_empty_responses: bool = False,
show_comp: bool = True,
show_rules: bool = False,
show_status: bool = True
) -> str:
"""
Get the full control implemented requirements, broken down based on the available control responses.
Args:
control_id: id of the control
level: level of indentation
write_empty_responses: write response even if empty
show_comp: show the component name in the response
Notes:
This is intended to be invoked from a jinja template that has already written out the prompt for
control response
"""
if not self._resolved_catalog:
raise TrestleError('Cannot get control response, set resolved catalog first.')
control = self._catalog_interface.get_control(control_id)
imp_req = self._control_implemented_req(control_id)
if not imp_req:
logger.info(f'No implemented requirements found for the control {control_id}')
return ''
md_writer = MDWriter(None)
system_prose = ''
system_rules = []
system_status = STATUS_OPERATIONAL
imp_req_responses = self._get_responses_by_components(imp_req, write_empty_responses)
if SSP_MAIN_COMP_NAME in imp_req_responses:
system_prose, system_rules, system_status = imp_req_responses[SSP_MAIN_COMP_NAME]
SSPMarkdownWriter._write_component_prompt(
md_writer, SSP_MAIN_COMP_NAME, system_prose, system_rules, system_status, show_rules, show_status
)
# if a control has no statement sub-parts then get the response bycomps from the imp_req itself
# otherwise get them from the statements in the imp_req
# an imp_req and a statement are both things that can have bycomps
has_bycomps = imp_req.statements if imp_req.statements else [imp_req]
for has_bycomp in has_bycomps:
statement_id = getattr(has_bycomp, 'statement_id', f'{control_id}_smt')
label = statement_id
part_name = None
# look up label for this statement
if control.parts:
found_label, part = self._catalog_interface.get_statement_label_if_exists(control_id, statement_id)
if found_label:
label = found_label
part_name = part.name
response_per_component = self._get_responses_by_components(has_bycomp, write_empty_responses)
if response_per_component or write_empty_responses:
if part_name and part_name == ITEM:
# print part header only if subitem
header = f'Implementation for part {label}'
md_writer.new_header(1, title=header)
for comp_name, comp_response in response_per_component.items():
if comp_name == SSP_MAIN_COMP_NAME:
continue
prose, rules, status = comp_response
if show_comp:
SSPMarkdownWriter._write_component_prompt(
md_writer, comp_name, prose, rules, status, show_rules, show_status
)
lines = md_writer.get_lines()
tree = DocsMarkdownNode.build_tree_from_markdown(lines)
tree.change_header_level_by(level)
return tree.content.raw_text
get_control_statement(self, control_id, level)
¤
Get the control statement for an ssp - to be printed in markdown as a structured list.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
control_id |
str |
The control_id to use. |
required |
Returns:
Type | Description |
---|---|
str |
A markdown blob as a string. |
Source code in trestle/core/ssp_io.py
def get_control_statement(self, control_id: str, level: int) -> str:
"""
Get the control statement for an ssp - to be printed in markdown as a structured list.
Args:
control_id: The control_id to use.
Returns:
A markdown blob as a string.
"""
if not self._resolved_catalog:
raise TrestleError('Cannot get control statement, set resolved catalog first.')
writer = DocsControlWriter()
control = self._catalog_interface.get_control(control_id)
if not control:
return ''
control_lines = writer.get_control_statement_ssp(control)
return self._build_tree_and_adjust(control_lines, level)
get_fedramp_control_origination(self, control_id, level)
¤
Print control origination, as a list of items, only showing those that are applicable for the control.
Using a LUT to map between structured data fields, defined by FedRAMP and historical text.
Source code in trestle/core/ssp_io.py
def get_fedramp_control_origination(self, control_id: str, level: int) -> str:
"""
Print control origination, as a list of items, only showing those that are applicable for the control.
Using a LUT to map between structured data fields, defined by FedRAMP and historical text.
"""
if not self._ssp:
raise TrestleError('Cannot get FedRamp control origination, set SSP first.')
control_origination = []
control_impl_req = self._control_implemented_req(control_id)
if control_impl_req and control_impl_req.props:
for prop in control_impl_req.props:
if prop.name == CONTROL_ORIGINATION:
control_origination.append(prop.value)
md_list = self._write_list_with_header('FedRamp Control Origination.', control_origination, level)
return md_list
get_fedramp_control_tables(self, control_id, level, label_column=False)
¤
Get the fedramp metadata as markdown tables, with optional third label column for params.
The fedramp metadata has the following elements: - Responsible roles field - Parameter values table - Implementation status field - Control origination field
Returns:
Type | Description |
---|---|
str |
tables as one coherent markdown blob. |
Source code in trestle/core/ssp_io.py
def get_fedramp_control_tables(self, control_id: str, level: int, label_column: bool = False) -> str:
"""Get the fedramp metadata as markdown tables, with optional third label column for params.
The fedramp metadata has the following elements:
- Responsible roles field
- Parameter values table
- Implementation status field
- Control origination field
Returns:
tables as one coherent markdown blob.
"""
resp_roles_table = self.get_responsible_roles_table(control_id, level)
params_values = self._parameter_table(control_id, level, label_column)
impl_status = self.get_fedramp_implementation_status(control_id, level)
control_orig = self.get_fedramp_control_origination(control_id, level)
final_output = ''
if resp_roles_table:
final_output += resp_roles_table
if params_values:
final_output += '\n' + params_values
if impl_status:
final_output += '\n' + impl_status
if control_orig:
final_output += '\n' + control_orig
return final_output
get_fedramp_implementation_status(self, control_id, level)
¤
Print implementation status as a list of items, only showing those that are applicable for the control.
This is unlike the word document FedRAMP which uses checkboxes on standard set of options. Using a LUT to map between structured data fields, defined by FedRAMP and historical text.
Source code in trestle/core/ssp_io.py
def get_fedramp_implementation_status(self, control_id: str, level: int) -> str:
"""
Print implementation status as a list of items, only showing those that are applicable for the control.
This is unlike the word document FedRAMP which uses checkboxes on standard set of options.
Using a LUT to map between structured data fields, defined by FedRAMP and historical text.
"""
if not self._ssp:
raise TrestleError('Cannot get Fedramp implementation status, set SSP first.')
implementation_statuses: List[str] = []
control_impl_req = self._control_implemented_req(control_id)
if control_impl_req and control_impl_req.props:
for prop in control_impl_req.props:
if prop.name == IMPLEMENTATION_STATUS:
implementation_statuses.append(prop.value)
md_list = self._write_list_with_header('FedRamp Implementation Status.', implementation_statuses, level)
return md_list
get_responsible_roles_table(self, control_id, level)
¤
For each role id - if the role exists in metadata use the title as what gets printed in the roles table.
If not (for now) warn and use the role-id as the printed text.
Source code in trestle/core/ssp_io.py
def get_responsible_roles_table(self, control_id: str, level: int) -> str:
"""
For each role id - if the role exists in metadata use the title as what gets printed in the roles table.
If not (for now) warn and use the role-id as the printed text.
"""
if self._ssp is None:
raise TrestleError('Cannot get responsible roles, SSP is not set.')
for impl_requirement in self._ssp.control_implementation.implemented_requirements:
if impl_requirement.control_id == control_id:
if impl_requirement.responsible_roles:
resp_roles = as_list(impl_requirement.responsible_roles)
role_ids = [role.role_id.replace('_', ' ') for role in resp_roles]
# now check if this role exists in the metadata
role_titles = dict(zip(role_ids, role_ids))
roles = as_list(self._ssp.metadata.roles)
for role in roles:
if role.id in role_ids:
role_titles[role.id] = role.title
# dictionary to md table
md_list = self._write_table_with_header(
'Responsible Roles.', [[key, role_titles[key]] for key in role_titles.keys()],
['Role ID', 'Title'],
level
)
return md_list
else:
logger.warning(
f'No responsible roles were found for the control with id: {control_id} in given SSP.'
)
return ''
return ''
set_catalog(self, resolved_catalog)
¤
Set catalog.
Source code in trestle/core/ssp_io.py
def set_catalog(self, resolved_catalog: Catalog) -> None:
"""Set catalog."""
self._resolved_catalog = resolved_catalog
self._catalog_interface = catalog_interface.CatalogInterface(self._resolved_catalog)
set_ssp(self, ssp)
¤
Set ssp.
Source code in trestle/core/ssp_io.py
def set_ssp(self, ssp: ssp.SystemSecurityPlan) -> None:
"""Set ssp."""
self._ssp = ssp
handler: python