feat(compliance): add KISA ISMS-P compliance framework (#5086)

Co-authored-by: MZC01-JUDE <mzc01-jude@MZC01-JUDE-2.local>
This commit is contained in:
Jude Bae(Bae cheongho)
2024-09-25 16:06:05 +09:00
committed by GitHub
parent 3fcccd0bcd
commit de5742433b
17 changed files with 9406 additions and 2 deletions
+229
View File
@@ -2223,3 +2223,232 @@ def get_section_containers_ens(data, section_1, section_2, section_3, section_4)
section_containers.append(section_container)
return html.Div(section_containers, className="compliance-data-layout")
# This function extracts and compares up to two numeric values, ensuring correct sorting for version-like strings.
def extract_numeric_values(value):
numbers = re.findall(r"\d+", str(value))
if len(numbers) >= 2:
return int(numbers[0]), int(numbers[1])
elif len(numbers) == 1:
return int(numbers[0]), 0
return 0, 0
def get_section_containers_kisa_ismsp(data, section_1, section_2):
data["STATUS"] = data["STATUS"].apply(map_status_to_icon)
data[section_1] = data[section_1].astype(str)
data[section_2] = data[section_2].astype(str)
data.sort_values(
by=section_1,
key=lambda x: x.map(extract_numeric_values),
ascending=True,
inplace=True,
)
findings_counts_section = (
data.groupby([section_2, "STATUS"]).size().unstack(fill_value=0)
)
findings_counts_name = (
data.groupby([section_1, "STATUS"]).size().unstack(fill_value=0)
)
section_containers = []
for name in data[section_1].unique():
success_name = (
findings_counts_name.loc[name, pass_emoji]
if pass_emoji in findings_counts_name.columns
else 0
)
failed_name = (
findings_counts_name.loc[name, fail_emoji]
if fail_emoji in findings_counts_name.columns
else 0
)
fig_name = go.Figure(
data=[
go.Bar(
name="Failed",
x=[failed_name],
y=[""],
orientation="h",
marker=dict(color="#e77676"),
width=[0.8],
),
go.Bar(
name="Success",
x=[success_name],
y=[""],
orientation="h",
marker=dict(color="#45cc6e"),
width=[0.8],
),
]
)
fig_name.update_layout(
barmode="stack",
margin=dict(l=10, r=10, t=10, b=10),
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
showlegend=False,
width=350,
height=30,
xaxis=dict(showticklabels=False, showgrid=False, zeroline=False),
yaxis=dict(showticklabels=False, showgrid=False, zeroline=False),
annotations=[
dict(
x=success_name + failed_name,
y=0,
xref="x",
yref="y",
text=str(success_name),
showarrow=False,
font=dict(color="#45cc6e", size=14),
xanchor="left",
yanchor="middle",
),
dict(
x=0,
y=0,
xref="x",
yref="y",
text=str(failed_name),
showarrow=False,
font=dict(color="#e77676", size=14),
xanchor="right",
yanchor="middle",
),
],
)
graph_name = dcc.Graph(
figure=fig_name, config={"staticPlot": True}, className="info-bar"
)
graph_div = html.Div(graph_name, className="graph-section")
direct_internal_items = []
for section in data[data[section_1] == name][section_2].unique():
specific_data = data[
(data[section_1] == name) & (data[section_2] == section)
]
success_section = (
findings_counts_section.loc[section, pass_emoji]
if pass_emoji in findings_counts_section.columns
else 0
)
failed_section = (
findings_counts_section.loc[section, fail_emoji]
if fail_emoji in findings_counts_section.columns
else 0
)
data_table = dash_table.DataTable(
data=specific_data.to_dict("records"),
columns=[
{"name": i, "id": i}
for i in ["CHECKID", "STATUS", "REGION", "ACCOUNTID", "RESOURCEID"]
],
style_table={"overflowX": "auto"},
style_as_list_view=True,
style_cell={"textAlign": "left", "padding": "5px"},
)
fig_section = go.Figure(
data=[
go.Bar(
name="Failed",
x=[failed_section],
y=[""],
orientation="h",
marker=dict(color="#e77676"),
),
go.Bar(
name="Success",
x=[success_section],
y=[""],
orientation="h",
marker=dict(color="#45cc6e"),
),
]
)
fig_section.update_layout(
barmode="stack",
margin=dict(l=10, r=10, t=10, b=10),
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
showlegend=False,
width=350,
height=30,
xaxis=dict(showticklabels=False, showgrid=False, zeroline=False),
yaxis=dict(showticklabels=False, showgrid=False, zeroline=False),
annotations=[
dict(
x=success_section + failed_section,
y=0,
xref="x",
yref="y",
text=str(success_section),
showarrow=False,
font=dict(color="#45cc6e", size=14),
xanchor="left",
yanchor="middle",
),
dict(
x=0,
y=0,
xref="x",
yref="y",
text=str(failed_section),
showarrow=False,
font=dict(color="#e77676", size=14),
xanchor="right",
yanchor="middle",
),
],
)
graph_section = dcc.Graph(
figure=fig_section,
config={"staticPlot": True},
className="info-bar-child",
)
graph_div_section = html.Div(graph_section, className="graph-section-req")
internal_accordion_item = dbc.AccordionItem(
title=section,
children=[html.Div([data_table], className="inner-accordion-content")],
)
internal_section_container = html.Div(
[
graph_div_section,
dbc.Accordion(
[internal_accordion_item], start_collapsed=True, flush=True
),
],
className="accordion-inner--child",
)
direct_internal_items.append(internal_section_container)
accordion_item = dbc.AccordionItem(
title=f"{name}", children=direct_internal_items
)
section_container = html.Div(
[
graph_div,
dbc.Accordion([accordion_item], start_collapsed=True, flush=True),
],
className="accordion-inner",
)
section_containers.append(section_container)
return html.Div(section_containers, className="compliance-data-layout")
@@ -0,0 +1,25 @@
import warnings
from dashboard.common_methods import get_section_containers_kisa_ismsp
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_ATTRIBUTES_SUBDOMAIN",
"REQUIREMENTS_ATTRIBUTES_SECTION",
# "REQUIREMENTS_DESCRIPTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_kisa_ismsp(
aux, "REQUIREMENTS_ATTRIBUTES_SUBDOMAIN", "REQUIREMENTS_ATTRIBUTES_SECTION"
)
@@ -0,0 +1,25 @@
import warnings
from dashboard.common_methods import get_section_containers_kisa_ismsp
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_ATTRIBUTES_SUBDOMAIN",
"REQUIREMENTS_ATTRIBUTES_SECTION",
# "REQUIREMENTS_DESCRIPTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_kisa_ismsp(
aux, "REQUIREMENTS_ATTRIBUTES_SUBDOMAIN", "REQUIREMENTS_ATTRIBUTES_SECTION"
)
+15
View File
@@ -54,6 +54,7 @@ from prowler.lib.outputs.compliance.compliance import display_compliance_table
from prowler.lib.outputs.compliance.ens.ens_aws import AWSENS
from prowler.lib.outputs.compliance.generic.generic import GenericCompliance
from prowler.lib.outputs.compliance.iso27001.iso27001_aws import AWSISO27001
from prowler.lib.outputs.compliance.kisa_ismsp.kisa_ismsp_aws import AWSKISAISMSP
from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_aws import AWSMitreAttack
from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_azure import (
AzureMitreAttack,
@@ -430,6 +431,20 @@ def prowler():
)
generated_outputs["compliance"].append(iso27001)
iso27001.batch_write_data_to_file()
elif compliance_name.startswith("kisa"):
# Generate KISA-ISMS-P Finding Object
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
)
kisa_ismsp = AWSKISAISMSP(
findings=finding_outputs,
compliance=bulk_compliance_frameworks[compliance_name],
create_file_descriptor=True,
file_path=filename,
)
generated_outputs["compliance"].append(kisa_ismsp)
kisa_ismsp.batch_write_data_to_file()
else:
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+14
View File
@@ -169,6 +169,19 @@ class Mitre_Requirement(BaseModel):
Checks: list[str]
# KISA-ISMS-P Requirement Attribute
class KISA_ISMSP_Requirement_Attribute(BaseModel):
"""KISA ISMS-P Requirement Attribute"""
Domain: str
Subdomain: str
Section: str
AuditChecklist: Optional[list[str]]
RelatedRegulations: Optional[list[str]]
AuditEvidence: Optional[list[str]]
NonComplianceCases: Optional[list[str]]
# Base Compliance Model
# TODO: move this to compliance folder
class Compliance_Requirement(BaseModel):
@@ -183,6 +196,7 @@ class Compliance_Requirement(BaseModel):
ENS_Requirement_Attribute,
ISO27001_2013_Requirement_Attribute,
AWS_Well_Architected_Requirement_Attribute,
KISA_ISMSP_Requirement_Attribute,
# Generic_Compliance_Requirement_Attribute must be the last one since it is the fallback for generic compliance framework
Generic_Compliance_Requirement_Attribute,
]
@@ -7,6 +7,7 @@ from prowler.lib.outputs.compliance.ens.ens import get_ens_table
from prowler.lib.outputs.compliance.generic.generic_table import (
get_generic_compliance_table,
)
from prowler.lib.outputs.compliance.kisa_ismsp.kisa_ismsp import get_kisa_ismsp_table
from prowler.lib.outputs.compliance.mitre_attack.mitre_attack import (
get_mitre_attack_table,
)
@@ -62,6 +63,15 @@ def display_compliance_table(
output_directory,
compliance_overview,
)
elif "kisa_isms_" in compliance_framework:
get_kisa_ismsp_table(
findings,
bulk_checks_metadata,
compliance_framework,
output_filename,
output_directory,
compliance_overview,
)
else:
get_generic_compliance_table(
findings,
@@ -0,0 +1,89 @@
from colorama import Fore, Style
from tabulate import tabulate
from prowler.config.config import orange_color
def get_kisa_ismsp_table(
findings: list,
bulk_checks_metadata: dict,
compliance_framework: str,
output_filename: str,
output_directory: str,
compliance_overview: bool,
):
sections = {}
kisa_ismsp_compliance_table = {
"Provider": [],
"Section": [],
"Status": [],
"Muted": [],
}
pass_count = []
fail_count = []
muted_count = []
for index, finding in enumerate(findings):
check = bulk_checks_metadata[finding.check_metadata.CheckID]
check_compliances = check.Compliance
for compliance in check_compliances:
if (
compliance.Framework.startswith("KISA")
and compliance.Version in compliance_framework
):
for requirement in compliance.Requirements:
for attribute in requirement.Attributes:
section = attribute.Section
# Check if Section exists
if section not in sections:
sections[section] = {
"Status": f"{Fore.GREEN}PASS{Style.RESET_ALL}",
"Muted": 0,
}
if finding.muted:
if index not in muted_count:
muted_count.append(index)
sections[section]["Muted"] += 1
else:
if finding.status == "FAIL" and index not in fail_count:
fail_count.append(index)
elif finding.status == "PASS" and index not in pass_count:
pass_count.append(index)
# Add results to table
sections = dict(sorted(sections.items()))
for section in sections:
kisa_ismsp_compliance_table["Provider"].append(compliance.Provider)
kisa_ismsp_compliance_table["Section"].append(section)
kisa_ismsp_compliance_table["Muted"].append(
f"{orange_color}{sections[section]['Muted']}{Style.RESET_ALL}"
)
if len(fail_count) + len(pass_count) + len(muted_count) > 1:
print(
f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:"
)
overview_table = [
[
f"{Fore.RED}{round(len(fail_count) / len(findings) * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}",
f"{Fore.GREEN}{round(len(pass_count) / len(findings) * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}",
f"{orange_color}{round(len(muted_count) / len(findings) * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}",
]
]
print(tabulate(overview_table, tablefmt="rounded_grid"))
if not compliance_overview:
print(
f"\nFramework {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Results:"
)
print(
tabulate(
kisa_ismsp_compliance_table,
headers="keys",
tablefmt="rounded_grid",
)
)
print(
f"{Style.BRIGHT}* Only sections containing results appear.{Style.RESET_ALL}"
)
print(f"\nDetailed results of {compliance_framework.upper()} are in:")
print(
f" - CSV: {output_directory}/compliance/{output_filename}_{compliance_framework}.csv\n"
)
@@ -0,0 +1,93 @@
from prowler.lib.check.compliance_models import Compliance
from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput
from prowler.lib.outputs.compliance.kisa_ismsp.models import AWSKISAISMSPModel
from prowler.lib.outputs.finding import Finding
class AWSKISAISMSP(ComplianceOutput):
"""
This class represents the AWS KISA-ISMS-P compliance output.
Attributes:
- _data (list): A list to store transformed data from findings.
- _file_descriptor (TextIOWrapper): A file descriptor to write data to a file.
Methods:
- transform: Transforms findings into AWS KISA-ISMS-P compliance format.
"""
def transform(
self,
findings: list[Finding],
compliance: Compliance,
compliance_name: str,
) -> None:
"""
Transforms a list of findings into AWS KISA-ISMS-P compliance format.
Parameters:
- findings (list): A list of findings.
- compliance (Compliance): A compliance model.
- compliance_name (str): The name of the compliance model.
Returns:
- None
"""
for finding in findings:
# Get the compliance requirements for the finding
finding_requirements = finding.compliance.get(compliance_name, [])
for requirement in compliance.Requirements:
if requirement.Id in finding_requirements:
for attribute in requirement.Attributes:
compliance_row = AWSKISAISMSPModel(
Provider=finding.provider,
Description=compliance.Description,
AccountId=finding.account_uid,
Region=finding.region,
AssessmentDate=str(finding.timestamp),
Requirements_Id=requirement.Id,
Requirements_Name=requirement.Name,
Requirements_Description=requirement.Description,
Requirements_Attributes_Domain=attribute.Domain,
Requirements_Attributes_Subdomain=attribute.Subdomain,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_AuditChecklist=attribute.AuditChecklist,
Requirements_Attributes_RelatedRegulations=attribute.RelatedRegulations,
Requirements_Attributes_AuditEvidence=attribute.AuditEvidence,
Requirements_Attributes_NonComplianceCases=attribute.NonComplianceCases,
Status=finding.status,
StatusExtended=finding.status_extended,
ResourceId=finding.resource_uid,
ResourceName=finding.resource_name,
CheckId=finding.check_id,
Muted=finding.muted,
)
self._data.append(compliance_row)
# Add manual requirements to the compliance output
for requirement in compliance.Requirements:
if not requirement.Checks:
for attribute in requirement.Attributes:
compliance_row = AWSKISAISMSPModel(
Provider=compliance.Provider.lower(),
Description=compliance.Description,
AccountId="",
Region="",
AssessmentDate=str(finding.timestamp),
Requirements_Id=requirement.Id,
Requirements_Name=requirement.Name,
Requirements_Description=requirement.Description,
Requirements_Attributes_Domain=attribute.Domain,
Requirements_Attributes_Subdomain=attribute.Subdomain,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_AuditChecklist=attribute.AuditChecklist,
Requirements_Attributes_RelatedRegulations=attribute.RelatedRegulations,
Requirements_Attributes_AuditEvidence=attribute.AuditEvidence,
Requirements_Attributes_NonComplianceCases=attribute.NonComplianceCases,
Status="MANUAL",
StatusExtended="Manual check",
ResourceId="manual_check",
ResourceName="Manual check",
CheckId="manual",
Muted=False,
)
self._data.append(compliance_row)
@@ -0,0 +1,31 @@
from typing import Optional
from pydantic import BaseModel
class AWSKISAISMSPModel(BaseModel):
"""
The AWS KISA-ISMS-P Model outputs findings in a format compliant with the AWS KISA-ISMS-P standard
"""
Provider: str
Description: str
AccountId: str
Region: str
AssessmentDate: str
Requirements_Id: str
Requirements_Name: str
Requirements_Description: str
Requirements_Attributes_Domain: str
Requirements_Attributes_Subdomain: str
Requirements_Attributes_Section: str
Requirements_Attributes_AuditChecklist: Optional[list[str]]
Requirements_Attributes_RelatedRegulations: Optional[list[str]]
Requirements_Attributes_AuditEvidence: Optional[list[str]]
Requirements_Attributes_NonComplianceCases: Optional[list[str]]
Status: str
StatusExtended: str
ResourceId: str
ResourceName: str
CheckId: str
Muted: bool
+2
View File
@@ -390,6 +390,8 @@ class Test_Config:
"fedramp_low_revision_4_aws",
"cis_2.0_gcp",
"cis_1.8_kubernetes",
"kisa_isms-p_2023_aws",
"kisa_isms-p_2023-korean_aws",
]
assert (
get_available_compliance_frameworks().sort() == compliance_frameworks.sort()
+2
View File
@@ -447,6 +447,8 @@ class TestASFF:
"si-4-5",
],
"FedRAMP-Low-Revision-4": ["ac-2", "au-2", "ca-7"],
"KISA-ISMS-P-2023": ["2.6.1"],
"KISA-ISMS-P-2023-korean": ["2.6.1"],
},
)
+71
View File
@@ -12,6 +12,7 @@ from prowler.lib.check.compliance_models import (
Mitre_Requirement_Attribute_AWS,
Mitre_Requirement_Attribute_Azure,
Mitre_Requirement_Attribute_GCP,
KISA_ISMSP_Requirement_Attribute,
)
CIS_1_4_AWS_NAME = "cis_1.4_aws"
@@ -632,3 +633,73 @@ NIST_800_53_REVISION_4_AWS = Compliance(
),
],
)
KISA_ISMSP_AWS_NAME = "kisa_isms-p_2023_aws"
KISA_ISMSP_AWS = Compliance(
Framework="KISA-ISMS-P",
Provider="AWS",
Version="2023",
Description="The ISMS-P certification, established by KISA Korea Internet & Security Agency",
Requirements=[
Compliance_Requirement(
Id="2.5.3",
Name="User Authentication",
Description="User access to information systems",
Attributes=[
KISA_ISMSP_Requirement_Attribute(
Domain="2. Protection Measure Requirements",
Subdomain="2.5. Authentication and Authorization Management",
Section="2.5.3 User Authentication",
AuditChecklist=[
"Is access to information systems and personal information controlled through secure authentication?",
"Are login attempt limitations enforced?",
],
RelatedRegulations=[
"Personal Information Protection Act, Article 29",
"Standards for Ensuring the Safety of Personal Information, Article 5",
],
AuditEvidence=[
"Login screen for information systems",
"Login failure message screen",
],
NonComplianceCases=[
"Case 1: Insufficient authentication when accessing information systems externally.",
"Case 2: No limitation on login failure attempts.",
],
)
],
Checks=[
"cloudwatch_log_metric_filter_authentication_failures",
"cognito_user_pool_mfa_enabled",
],
),
Compliance_Requirement(
Id="2.5.4",
Name="User Authentication",
Description="User access to information systems",
Attributes=[
KISA_ISMSP_Requirement_Attribute(
Domain="2. Protection Measure Requirements",
Subdomain="2.5. Authentication and Authorization Management",
Section="2.5.3 User Authentication",
AuditChecklist=[
"Is access to information systems and personal information controlled through secure authentication?",
"Are login attempt limitations enforced?",
],
RelatedRegulations=[
"Personal Information Protection Act, Article 29",
"Standards for Ensuring the Safety of Personal Information, Article 5",
],
AuditEvidence=[
"Login screen for information systems",
"Login failure message screen",
],
NonComplianceCases=[
"Case 1: Insufficient authentication when accessing information systems externally.",
"Case 2: No limitation on login failure attempts.",
],
)
],
Checks=[],
),
],
)
@@ -0,0 +1,122 @@
from datetime import datetime
from io import StringIO
from freezegun import freeze_time
from mock import patch
from prowler.lib.outputs.compliance.kisa_ismsp.kisa_ismsp_aws import AWSKISAISMSP
from prowler.lib.outputs.compliance.kisa_ismsp.models import AWSKISAISMSPModel
from tests.lib.outputs.compliance.fixtures import KISA_ISMSP_AWS
from tests.lib.outputs.fixtures.fixtures import generate_finding_output
from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_EU_WEST_1
class TestAWSKISAISMSP:
def test_output_transform(self):
findings = [generate_finding_output(compliance={"KISA-ISMS-P-2023": ["2.5.3"]})]
output = AWSKISAISMSP(findings, KISA_ISMSP_AWS)
output_data = output.data[0]
assert isinstance(output_data, AWSKISAISMSPModel)
assert output_data.Provider == "aws"
assert output_data.AccountId == AWS_ACCOUNT_NUMBER
assert output_data.Region == AWS_REGION_EU_WEST_1
assert output_data.Description == KISA_ISMSP_AWS.Description
assert output_data.Requirements_Id == KISA_ISMSP_AWS.Requirements[0].Id
assert (
output_data.Requirements_Description
== KISA_ISMSP_AWS.Requirements[0].Description
)
assert (
output_data.Requirements_Attributes_Domain
== KISA_ISMSP_AWS.Requirements[0].Attributes[0].Domain
)
assert (
output_data.Requirements_Attributes_Subdomain
== KISA_ISMSP_AWS.Requirements[0].Attributes[0].Subdomain
)
assert (
output_data.Requirements_Attributes_Section
== KISA_ISMSP_AWS.Requirements[0].Attributes[0].Section
)
assert (
output_data.Requirements_Attributes_AuditChecklist
== KISA_ISMSP_AWS.Requirements[0].Attributes[0].AuditChecklist
)
assert (
output_data.Requirements_Attributes_RelatedRegulations
== KISA_ISMSP_AWS.Requirements[0].Attributes[0].RelatedRegulations
)
assert (
output_data.Requirements_Attributes_AuditEvidence
== KISA_ISMSP_AWS.Requirements[0].Attributes[0].AuditEvidence
)
assert (
output_data.Requirements_Attributes_NonComplianceCases
== KISA_ISMSP_AWS.Requirements[0].Attributes[0].NonComplianceCases
)
assert output_data.Status == "PASS"
assert output_data.StatusExtended == ""
assert output_data.ResourceId == ""
assert output_data.ResourceName == ""
assert output_data.CheckId == "test-check-id"
assert output_data.Muted is False
# Test manual check
output_data_manual = output.data[1]
assert output_data_manual.Provider == "aws"
assert output_data_manual.AccountId == ""
assert output_data_manual.Region == ""
assert output_data_manual.Requirements_Id == KISA_ISMSP_AWS.Requirements[1].Id
assert (
output_data_manual.Requirements_Description
== KISA_ISMSP_AWS.Requirements[1].Description
)
assert (
output_data_manual.Requirements_Attributes_Domain
== KISA_ISMSP_AWS.Requirements[1].Attributes[0].Domain
)
assert (
output_data_manual.Requirements_Attributes_Subdomain
== KISA_ISMSP_AWS.Requirements[1].Attributes[0].Subdomain
)
assert (
output_data_manual.Requirements_Attributes_Section
== KISA_ISMSP_AWS.Requirements[1].Attributes[0].Section
)
assert (
output_data_manual.Requirements_Attributes_AuditChecklist
== KISA_ISMSP_AWS.Requirements[1].Attributes[0].AuditChecklist
)
assert (
output_data_manual.Requirements_Attributes_RelatedRegulations
== KISA_ISMSP_AWS.Requirements[1].Attributes[0].RelatedRegulations
)
assert (
output_data_manual.Requirements_Attributes_AuditEvidence
== KISA_ISMSP_AWS.Requirements[1].Attributes[0].AuditEvidence
)
assert (
output_data_manual.Requirements_Attributes_NonComplianceCases
== KISA_ISMSP_AWS.Requirements[1].Attributes[0].NonComplianceCases
)
assert output_data_manual.Status == "MANUAL"
assert output_data_manual.StatusExtended == "Manual check"
assert output_data_manual.ResourceId == "manual_check"
assert output_data_manual.ResourceName == "Manual check"
assert output_data_manual.CheckId == "manual"
assert output_data_manual.Muted is False
@freeze_time(datetime.now())
def test_batch_write_data_to_file(self):
mock_file = StringIO()
findings = [generate_finding_output(compliance={"KISA-ISMS-P-2023": ["2.5.3"]})]
output = AWSKISAISMSP(findings, KISA_ISMSP_AWS)
output._file_descriptor = mock_file
with patch.object(mock_file, "close", return_value=None):
output.batch_write_data_to_file()
mock_file.seek(0)
content = mock_file.read()
expected_csv = f"PROVIDER;DESCRIPTION;ACCOUNTID;REGION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_NAME;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_DOMAIN;REQUIREMENTS_ATTRIBUTES_SUBDOMAIN;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_AUDITCHECKLIST;REQUIREMENTS_ATTRIBUTES_RELATEDREGULATIONS;REQUIREMENTS_ATTRIBUTES_AUDITEVIDENCE;REQUIREMENTS_ATTRIBUTES_NONCOMPLIANCECASES;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\naws;The ISMS-P certification, established by KISA Korea Internet & Security Agency;123456789012;eu-west-1;{datetime.now()};2.5.3;User Authentication;User access to information systems;2. Protection Measure Requirements;2.5. Authentication and Authorization Management;2.5.3 User Authentication;['Is access to information systems and personal information controlled through secure authentication?', 'Are login attempt limitations enforced?'];['Personal Information Protection Act, Article 29', 'Standards for Ensuring the Safety of Personal Information, Article 5'];['Login screen for information systems', 'Login failure message screen'];['Case 1: Insufficient authentication when accessing information systems externally.', 'Case 2: No limitation on login failure attempts.'];PASS;;;;test-check-id;False\r\naws;The ISMS-P certification, established by KISA Korea Internet & Security Agency;;;{datetime.now()};2.5.4;User Authentication;User access to information systems;2. Protection Measure Requirements;2.5. Authentication and Authorization Management;2.5.3 User Authentication;['Is access to information systems and personal information controlled through secure authentication?', 'Are login attempt limitations enforced?'];['Personal Information Protection Act, Article 29', 'Standards for Ensuring the Safety of Personal Information, Article 5'];['Login screen for information systems', 'Login failure message screen'];['Case 1: Insufficient authentication when accessing information systems externally.', 'Case 2: No limitation on login failure attempts.'];MANUAL;Manual check;manual_check;Manual check;manual;False\r\n"
assert content == expected_csv
+8 -2
View File
@@ -49,7 +49,7 @@ class TestOutputs:
assert unroll_list(list, ",") == "test, test1, test2"
def test_parse_html_string(self):
string = "CISA: your-systems-3, your-data-1, your-data-2 | CIS-1.4: 2.1.1 | CIS-1.5: 2.1.1 | GDPR: article_32 | AWS-Foundational-Security-Best-Practices: s3 | HIPAA: 164_308_a_1_ii_b, 164_308_a_4_ii_a, 164_312_a_2_iv, 164_312_c_1, 164_312_c_2, 164_312_e_2_ii | GxP-21-CFR-Part-11: 11.10-c, 11.30 | GxP-EU-Annex-11: 7.1-data-storage-damage-protection | NIST-800-171-Revision-2: 3_3_8, 3_5_10, 3_13_11, 3_13_16 | NIST-800-53-Revision-4: sc_28 | NIST-800-53-Revision-5: au_9_3, cm_6_a, cm_9_b, cp_9_d, cp_9_8, pm_11_b, sc_8_3, sc_8_4, sc_13_a, sc_16_1, sc_28_1, si_19_4 | ENS-RD2022: mp.si.2.aws.s3.1 | NIST-CSF-1.1: ds_1 | RBI-Cyber-Security-Framework: annex_i_1_3 | FFIEC: d3-pc-am-b-12 | PCI-3.2.1: s3 | FedRamp-Moderate-Revision-4: sc-13, sc-28 | FedRAMP-Low-Revision-4: sc-13"
string = "CISA: your-systems-3, your-data-1, your-data-2 | CIS-1.4: 2.1.1 | CIS-1.5: 2.1.1 | GDPR: article_32 | AWS-Foundational-Security-Best-Practices: s3 | HIPAA: 164_308_a_1_ii_b, 164_308_a_4_ii_a, 164_312_a_2_iv, 164_312_c_1, 164_312_c_2, 164_312_e_2_ii | GxP-21-CFR-Part-11: 11.10-c, 11.30 | GxP-EU-Annex-11: 7.1-data-storage-damage-protection | NIST-800-171-Revision-2: 3_3_8, 3_5_10, 3_13_11, 3_13_16 | NIST-800-53-Revision-4: sc_28 | NIST-800-53-Revision-5: au_9_3, cm_6_a, cm_9_b, cp_9_d, cp_9_8, pm_11_b, sc_8_3, sc_8_4, sc_13_a, sc_16_1, sc_28_1, si_19_4 | ENS-RD2022: mp.si.2.aws.s3.1 | NIST-CSF-1.1: ds_1 | RBI-Cyber-Security-Framework: annex_i_1_3 | FFIEC: d3-pc-am-b-12 | PCI-3.2.1: s3 | FedRamp-Moderate-Revision-4: sc-13, sc-28 | FedRAMP-Low-Revision-4: sc-13 | KISA-ISMS-P-2023: 2.6.1 | KISA-ISMS-P-2023-korean: 2.6.1"
assert (
parse_html_string(string)
== """
@@ -88,6 +88,10 @@ class TestOutputs:
&#x2022;FedRamp-Moderate-Revision-4: sc-13, sc-28
&#x2022;FedRAMP-Low-Revision-4: sc-13
&#x2022;KISA-ISMS-P-2023: 2.6.1
&#x2022;KISA-ISMS-P-2023-korean: 2.6.1
"""
)
@@ -204,10 +208,12 @@ class TestOutputs:
"PCI-3.2.1": ["s3"],
"FedRamp-Moderate-Revision-4": ["sc-13", "sc-28"],
"FedRAMP-Low-Revision-4": ["sc-13"],
"KISA-ISMS-P-2023": ["2.6.1"],
"KISA-ISMS-P-2023-korean": ["2.6.1"],
}
assert (
unroll_dict(test_compliance_dict, separator=": ")
== "CISA: your-systems-3, your-data-1, your-data-2 | CIS-1.4: 2.1.1 | CIS-1.5: 2.1.1 | GDPR: article_32 | AWS-Foundational-Security-Best-Practices: s3 | HIPAA: 164_308_a_1_ii_b, 164_308_a_4_ii_a, 164_312_a_2_iv, 164_312_c_1, 164_312_c_2, 164_312_e_2_ii | GxP-21-CFR-Part-11: 11.10-c, 11.30 | GxP-EU-Annex-11: 7.1-data-storage-damage-protection | NIST-800-171-Revision-2: 3_3_8, 3_5_10, 3_13_11, 3_13_16 | NIST-800-53-Revision-4: sc_28 | NIST-800-53-Revision-5: au_9_3, cm_6_a, cm_9_b, cp_9_d, cp_9_8, pm_11_b, sc_8_3, sc_8_4, sc_13_a, sc_16_1, sc_28_1, si_19_4 | ENS-RD2022: mp.si.2.aws.s3.1 | NIST-CSF-1.1: ds_1 | RBI-Cyber-Security-Framework: annex_i_1_3 | FFIEC: d3-pc-am-b-12 | PCI-3.2.1: s3 | FedRamp-Moderate-Revision-4: sc-13, sc-28 | FedRAMP-Low-Revision-4: sc-13"
== "CISA: your-systems-3, your-data-1, your-data-2 | CIS-1.4: 2.1.1 | CIS-1.5: 2.1.1 | GDPR: article_32 | AWS-Foundational-Security-Best-Practices: s3 | HIPAA: 164_308_a_1_ii_b, 164_308_a_4_ii_a, 164_312_a_2_iv, 164_312_c_1, 164_312_c_2, 164_312_e_2_ii | GxP-21-CFR-Part-11: 11.10-c, 11.30 | GxP-EU-Annex-11: 7.1-data-storage-damage-protection | NIST-800-171-Revision-2: 3_3_8, 3_5_10, 3_13_11, 3_13_16 | NIST-800-53-Revision-4: sc_28 | NIST-800-53-Revision-5: au_9_3, cm_6_a, cm_9_b, cp_9_d, cp_9_8, pm_11_b, sc_8_3, sc_8_4, sc_13_a, sc_16_1, sc_28_1, si_19_4 | ENS-RD2022: mp.si.2.aws.s3.1 | NIST-CSF-1.1: ds_1 | RBI-Cyber-Security-Framework: annex_i_1_3 | FFIEC: d3-pc-am-b-12 | PCI-3.2.1: s3 | FedRamp-Moderate-Revision-4: sc-13, sc-28 | FedRAMP-Low-Revision-4: sc-13 | KISA-ISMS-P-2023: 2.6.1 | KISA-ISMS-P-2023-korean: 2.6.1"
)
def test_unroll_dict_to_list(self):