Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions application/frontend/src/pages/GapAnalysis/GapAnalysis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -481,8 +481,8 @@ export const GapAnalysis = () => {
<Table.Header>
<Table.Row>
<Table.HeaderCell width={2}>Rank</Table.HeaderCell>
<Table.HeaderCell width={6}>OWASP Top 10 2021</Table.HeaderCell>
<Table.HeaderCell width={6}>OWASP Top 10 2025</Table.HeaderCell>
<Table.HeaderCell width={6}>{owaspComparison.standards[0]}</Table.HeaderCell>
<Table.HeaderCell width={6}>{owaspComparison.standards[1]}</Table.HeaderCell>
<Table.HeaderCell width={2}>Changed</Table.HeaderCell>
</Table.Row>
</Table.Header>
Expand All @@ -493,21 +493,21 @@ export const GapAnalysis = () => {
<b>{item.rank}</b>
</Table.Cell>
<Table.Cell>
{item.top10_2021?.hyperlink ? (
<a href={item.top10_2021.hyperlink} target="_blank" rel="noopener noreferrer">
{item.top10_2021.section}
{item.left?.hyperlink ? (
<a href={item.left.hyperlink} target="_blank" rel="noopener noreferrer">
{item.left.section}
</a>
) : (
item.top10_2021?.section ?? <i>Not mapped</i>
item.left?.section ?? <i>Not mapped</i>
)}
</Table.Cell>
<Table.Cell>
{item.top10_2025?.hyperlink ? (
<a href={item.top10_2025.hyperlink} target="_blank" rel="noopener noreferrer">
{item.top10_2025.section}
{item.right?.hyperlink ? (
<a href={item.right.hyperlink} target="_blank" rel="noopener noreferrer">
{item.right.section}
</a>
) : (
item.top10_2025?.section ?? <i>Not mapped</i>
item.right?.section ?? <i>Not mapped</i>
)}
</Table.Cell>
<Table.Cell>{item.changed ? 'Yes' : 'No'}</Table.Cell>
Expand Down
4 changes: 2 additions & 2 deletions application/frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ export interface OwaspTop10ComparisonItemEntry {
export interface OwaspTop10ComparisonItem {
rank: string;
changed: boolean;
top10_2021?: OwaspTop10ComparisonItemEntry;
top10_2025?: OwaspTop10ComparisonItemEntry;
left?: OwaspTop10ComparisonItemEntry;
right?: OwaspTop10ComparisonItemEntry;
}

export interface OwaspTop10Comparison {
Expand Down
8 changes: 8 additions & 0 deletions application/tests/owasp_kubernetes_top10_2025_parser_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,18 @@ def test_parse(self) -> None:
self.assertEqual(10, len(entries))
self.assertEqual("K01", entries[0].sectionID)
self.assertEqual("Insecure Workload Configurations", entries[0].section)
self.assertEqual(
"https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K01-Insecure-Workload-Configurations.html",
entries[0].hyperlink,
)
self.assertEqual(
["233-748", "486-813"], [l.document.id for l in entries[0].links]
)
self.assertEqual("K10", entries[-1].sectionID)
self.assertEqual(
"https://owasp.org/www-project-kubernetes-top-ten/2025/en/src/K10-Inadequate-Logging-And-Monitoring.html",
entries[-1].hyperlink,
)
self.assertEqual(
["148-420", "402-706", "843-841"],
[l.document.id for l in entries[-1].links],
Expand Down
237 changes: 233 additions & 4 deletions application/tests/web_main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,11 +772,11 @@ def get_nodes_side_effect(name=None, **kwargs):
)
self.assertEqual(
"Broken Access Controls",
payload["owasp_top10_comparison"]["items"][0]["top10_2021"]["section"],
payload["owasp_top10_comparison"]["items"][0]["left"]["section"],
)
self.assertEqual(
"Broken Access Control",
payload["owasp_top10_comparison"]["items"][0]["top10_2025"]["section"],
payload["owasp_top10_comparison"]["items"][0]["right"]["section"],
)

@patch.object(web_main.gap_analysis, "schedule")
Expand Down Expand Up @@ -824,7 +824,7 @@ def get_nodes_side_effect(name=None, **kwargs):
self.assertIn("owasp_top10_comparison", payload)
self.assertEqual(
"Broken Access Controls",
payload["owasp_top10_comparison"]["items"][0]["top10_2021"]["section"],
payload["owasp_top10_comparison"]["items"][0]["left"]["section"],
)

@patch.object(web_main.cre_main, "fetch_upstream_json")
Expand Down Expand Up @@ -1078,6 +1078,157 @@ def test_gap_analysis_adds_specialized_section_for_api_cheatsheets(
)
self.assertIn(base.id, payload["specialized_cheatsheet_section"]["result"])

@patch.object(web_main.cre_main, "fetch_upstream_json")
@patch.object(web_main.gap_analysis, "schedule")
@patch.object(db, "Node_collection")
def test_gap_analysis_adds_specialized_section_for_kubernetes_2022_cheatsheets(
self, db_mock, schedule_mock, upstream_fetch_mock
) -> None:
network_cre = defs.CRE(
id="132-146", name="Network segmentation", description=""
)
base = defs.Standard(
name="OWASP Kubernetes Top Ten 2022",
sectionID="K07",
section="Missing Network Segmentation Controls",
)
base.add_link(
defs.Link(ltype=defs.LinkTypes.LinkedTo, document=network_cre.shallow_copy())
)

compare = defs.Standard(
name="OWASP Cheat Sheets",
section="Kubernetes Security Cheat Sheet",
)
compare.add_link(
defs.Link(ltype=defs.LinkTypes.LinkedTo, document=network_cre.shallow_copy())
)

db_mock.return_value.get_gap_analysis_result.return_value = None
db_mock.return_value.gap_analysis_exists.return_value = False
db_mock.return_value.get_nodes.side_effect = lambda name=None, **kwargs: (
[base]
if name == "OWASP Kubernetes Top Ten 2022"
else [compare] if name == "OWASP Cheat Sheets" else []
)
schedule_mock.side_effect = RuntimeError("redis unavailable")
upstream_fetch_mock.side_effect = RuntimeError("upstream unavailable")

with self.app.test_client() as client:
response = client.get(
"/rest/v1/map_analysis?standard=OWASP%20Kubernetes%20Top%20Ten%202022&standard=OWASP%20Cheat%20Sheets",
headers={"Content-Type": "application/json"},
)

payload = json.loads(response.data)
self.assertEqual(200, response.status_code)
self.assertEqual(
"Cloud Cheat Sheets",
payload["specialized_cheatsheet_section"]["category"],
)
self.assertIn(base.id, payload["specialized_cheatsheet_section"]["result"])
self.assertIn(compare.id, payload["result"][base.id]["paths"])

@patch.object(web_main.cre_main, "fetch_upstream_json")
@patch.object(web_main.gap_analysis, "schedule")
@patch.object(db, "Node_collection")
def test_gap_analysis_adds_specialized_section_for_kubernetes_2025_cheatsheets(
self, db_mock, schedule_mock, upstream_fetch_mock
) -> None:
network_cre = defs.CRE(
id="132-146", name="Network segmentation", description=""
)
base = defs.Standard(
name="OWASP Kubernetes Top Ten 2025 (Draft)",
sectionID="K05",
section="Missing Network Segmentation Controls",
)
base.add_link(
defs.Link(ltype=defs.LinkTypes.LinkedTo, document=network_cre.shallow_copy())
)

compare = defs.Standard(
name="OWASP Cheat Sheets",
section="Kubernetes Security Cheat Sheet",
)
compare.add_link(
defs.Link(ltype=defs.LinkTypes.LinkedTo, document=network_cre.shallow_copy())
)

db_mock.return_value.get_gap_analysis_result.return_value = None
db_mock.return_value.gap_analysis_exists.return_value = False
db_mock.return_value.get_nodes.side_effect = lambda name=None, **kwargs: (
[base]
if name == "OWASP Kubernetes Top Ten 2025 (Draft)"
else [compare] if name == "OWASP Cheat Sheets" else []
)
schedule_mock.side_effect = RuntimeError("redis unavailable")
upstream_fetch_mock.side_effect = RuntimeError("upstream unavailable")

with self.app.test_client() as client:
response = client.get(
"/rest/v1/map_analysis?standard=OWASP%20Kubernetes%20Top%20Ten%202025%20%28Draft%29&standard=OWASP%20Cheat%20Sheets",
headers={"Content-Type": "application/json"},
)

payload = json.loads(response.data)
self.assertEqual(200, response.status_code)
self.assertEqual(
"Cloud Cheat Sheets",
payload["specialized_cheatsheet_section"]["category"],
)
self.assertIn(base.id, payload["specialized_cheatsheet_section"]["result"])
self.assertIn(compare.id, payload["result"][base.id]["paths"])

@patch.object(web_main.cre_main, "fetch_upstream_json")
@patch.object(web_main.gap_analysis, "schedule")
@patch.object(db, "Node_collection")
def test_gap_analysis_returns_direct_overlap_for_kubernetes_and_ccm(
self, db_mock, schedule_mock, upstream_fetch_mock
) -> None:
shared_cre = defs.CRE(
id="117-371", name="Centralized policy enforcement", description=""
)
base = defs.Standard(
name="OWASP Kubernetes Top Ten 2022",
sectionID="K04",
section="Lack of Centralized Policy Enforcement",
)
base.add_link(
defs.Link(ltype=defs.LinkTypes.LinkedTo, document=shared_cre.shallow_copy())
)

compare = defs.Standard(
name="Cloud Controls Matrix",
sectionID="AIS-01",
section="Application and Interface Security",
)
compare.add_link(
defs.Link(ltype=defs.LinkTypes.LinkedTo, document=shared_cre.shallow_copy())
)

db_mock.return_value.get_gap_analysis_result.return_value = None
db_mock.return_value.gap_analysis_exists.return_value = False
db_mock.return_value.get_nodes.side_effect = lambda name=None, **kwargs: (
[base]
if name == "OWASP Kubernetes Top Ten 2022"
else [compare] if name == "Cloud Controls Matrix" else []
)
schedule_mock.side_effect = RuntimeError("redis unavailable")
upstream_fetch_mock.side_effect = RuntimeError("upstream unavailable")

with self.app.test_client() as client:
response = client.get(
"/rest/v1/map_analysis?standard=OWASP%20Kubernetes%20Top%20Ten%202022&standard=Cloud%20Controls%20Matrix",
headers={"Content-Type": "application/json"},
)

payload = json.loads(response.data)
self.assertEqual(200, response.status_code)
self.assertIn("result", payload)
self.assertIn(base.id, payload["result"])
self.assertIn(compare.id, payload["result"][base.id]["paths"])

@patch.object(web_main.gap_analysis, "schedule")
@patch.object(db, "Node_collection")
def test_gap_analysis_supports_opencre_as_standard(
Expand Down Expand Up @@ -1312,12 +1463,90 @@ def get_nodes_side_effect(name=None, **kwargs):
self.assertIn("owasp_top10_comparison", payload)
self.assertEqual(
"Broken Access Control",
payload["owasp_top10_comparison"]["items"][0]["top10_2025"]["section"],
payload["owasp_top10_comparison"]["items"][0]["right"]["section"],
)
schedule_mock.assert_called_once_with(
["OWASP Top 10 2021", "OWASP Top 10 2025"], db_mock.return_value
)

@patch.object(web_main.gap_analysis, "schedule")
@patch.object(db, "Node_collection")
def test_gap_analysis_adds_kubernetes_comparison_section(
self, db_mock, schedule_mock
) -> None:
kubernetes_2022 = [
defs.Standard(
name="OWASP Kubernetes Top Ten 2022",
sectionID="K01",
section="Insecure Workload Configurations",
hyperlink="https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/K01-insecure-workload-configurations",
),
defs.Standard(
name="OWASP Kubernetes Top Ten 2022",
sectionID="K02",
section="Supply Chain Vulnerabilities",
hyperlink="https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/K02-supply-chain-vulnerabilities",
),
]
kubernetes_2025 = [
defs.Standard(
name="OWASP Kubernetes Top Ten 2025 (Draft)",
sectionID="K01",
section="Insecure Workload Configurations",
hyperlink="https://owasp.org/www-project-kubernetes-top-ten/",
),
defs.Standard(
name="OWASP Kubernetes Top Ten 2025 (Draft)",
sectionID="K02",
section="Overly Permissive Authorization Configurations",
hyperlink="https://owasp.org/www-project-kubernetes-top-ten/",
),
]
db_mock.return_value.get_gap_analysis_result.return_value = None
db_mock.return_value.gap_analysis_exists.return_value = False

def get_nodes_side_effect(name=None, **kwargs):
if name == "OWASP Kubernetes Top Ten 2022":
return kubernetes_2022
if name == "OWASP Kubernetes Top Ten 2025 (Draft)":
return kubernetes_2025
return []

db_mock.return_value.get_nodes.side_effect = get_nodes_side_effect
schedule_mock.side_effect = RuntimeError("redis unavailable")

with self.app.test_client() as client:
response = client.get(
"/rest/v1/map_analysis?standard=OWASP%20Kubernetes%20Top%20Ten%202022&standard=OWASP%20Kubernetes%20Top%20Ten%202025%20%28Draft%29",
headers={"Content-Type": "application/json"},
)

payload = json.loads(response.data)
self.assertEqual(200, response.status_code)
self.assertIn("owasp_top10_comparison", payload)
self.assertEqual(
[
"OWASP Kubernetes Top Ten 2022",
"OWASP Kubernetes Top Ten 2025 (Draft)",
],
payload["owasp_top10_comparison"]["standards"],
)
self.assertEqual(
"Supply Chain Vulnerabilities",
payload["owasp_top10_comparison"]["items"][1]["left"]["section"],
)
self.assertEqual(
"Overly Permissive Authorization Configurations",
payload["owasp_top10_comparison"]["items"][1]["right"]["section"],
)
schedule_mock.assert_called_once_with(
[
"OWASP Kubernetes Top Ten 2022",
"OWASP Kubernetes Top Ten 2025 (Draft)",
],
db_mock.return_value,
)

@patch.object(db, "Node_collection")
@patch.object(rq.job.Job, "fetch")
@patch.object(rq.Queue, "enqueue_call")
Expand Down
Loading