mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-08-19 09:30:21 +00:00
feat(github): scale organization_repository_creation_limited severity by repository visibility (#12164)
Co-authored-by: Lydia Vilchez <lydiavilchezlopez@gmail.com>
This commit is contained in:
co-authored by
Lydia Vilchez
parent
9daca2e4df
commit
286685a4f3
@@ -0,0 +1 @@
|
||||
GitHub `organization_repository_creation_limited` check now reports low severity for FAIL findings when repository creation is provably limited to private/internal visibility, instead of always reporting high
|
||||
+43
-5
@@ -1,6 +1,6 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportGithub
|
||||
from prowler.lib.check.models import Check, CheckReportGithub, Severity
|
||||
from prowler.providers.github.services.organization.organization_client import (
|
||||
organization_client,
|
||||
)
|
||||
@@ -15,8 +15,19 @@ def _join_human_readable(items: List[str]) -> str:
|
||||
return ", ".join(items[:-1]) + f" and {items[-1]}"
|
||||
|
||||
|
||||
PUBLIC_CREATION_TYPES = {"all", "public"}
|
||||
NON_PUBLIC_CREATION_TYPES = {"private", "internal"}
|
||||
PUBLIC_DISABLED_CREATION_TYPES = NON_PUBLIC_CREATION_TYPES | {"none"}
|
||||
KNOWN_CREATION_TYPES = PUBLIC_CREATION_TYPES | PUBLIC_DISABLED_CREATION_TYPES
|
||||
|
||||
|
||||
class organization_repository_creation_limited(Check):
|
||||
"""Check if repository creation is limited to trusted organization members."""
|
||||
"""Check if repository creation is limited to trusted organization members.
|
||||
|
||||
FAIL severity scales with the visibility members can create: high when public
|
||||
repository creation is (or may be) allowed, low when it is provably limited to
|
||||
private/internal repositories.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportGithub]:
|
||||
findings = []
|
||||
@@ -48,12 +59,19 @@ class organization_repository_creation_limited(Check):
|
||||
org, "members_allowed_repository_creation_type", None
|
||||
)
|
||||
|
||||
normalized_type = creation_type.lower() if creation_type else ""
|
||||
|
||||
type_flags = []
|
||||
enabled_types = []
|
||||
|
||||
if global_creation is not None:
|
||||
if global_creation:
|
||||
enabled_types.append("repositories of any type")
|
||||
public_known_disabled = (
|
||||
public_creation is False
|
||||
or normalized_type in PUBLIC_DISABLED_CREATION_TYPES
|
||||
)
|
||||
if not public_known_disabled:
|
||||
enabled_types.append("repositories of any type")
|
||||
else:
|
||||
type_flags.append(False)
|
||||
|
||||
@@ -70,7 +88,6 @@ class organization_repository_creation_limited(Check):
|
||||
enabled_types.append(label)
|
||||
|
||||
if creation_type:
|
||||
normalized_type = creation_type.lower()
|
||||
if normalized_type == "none":
|
||||
type_flags.append(False)
|
||||
else:
|
||||
@@ -97,7 +114,28 @@ class organization_repository_creation_limited(Check):
|
||||
unique_enabled = list(dict.fromkeys(enabled_types))
|
||||
allowed_desc = _join_human_readable(unique_enabled)
|
||||
if allowed_desc:
|
||||
report.status_extended = f"Organization {org.name} allows members to create {allowed_desc}."
|
||||
public_allowed = (
|
||||
public_creation is True
|
||||
or normalized_type in PUBLIC_CREATION_TYPES
|
||||
)
|
||||
non_public_allowed = (
|
||||
private_creation is True
|
||||
or internal_creation is True
|
||||
or normalized_type in NON_PUBLIC_CREATION_TYPES
|
||||
)
|
||||
public_known = (
|
||||
public_creation is not None
|
||||
or normalized_type in KNOWN_CREATION_TYPES
|
||||
)
|
||||
|
||||
if not public_allowed and non_public_allowed and public_known:
|
||||
report.check_metadata.Severity = Severity.low
|
||||
report.status_extended = (
|
||||
f"Organization {org.name} allows members to create {allowed_desc}. "
|
||||
"Public repository creation is disabled."
|
||||
)
|
||||
else:
|
||||
report.status_extended = f"Organization {org.name} allows members to create {allowed_desc}."
|
||||
else:
|
||||
report.status_extended = f"Organization {org.name} does not have enough data to confirm repository creation restrictions."
|
||||
|
||||
|
||||
+407
@@ -1,5 +1,6 @@
|
||||
from unittest import mock
|
||||
|
||||
from prowler.lib.check.models import Severity
|
||||
from prowler.providers.github.services.organization.organization_service import Org
|
||||
from tests.providers.github.github_fixtures import set_mocked_github_provider
|
||||
|
||||
@@ -58,6 +59,7 @@ class Test_organization_repository_creation_limited:
|
||||
assert len(result) == 1
|
||||
assert result[0].resource_name == org_name
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].check_metadata.Severity == Severity.high
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Organization {org_name} has disabled repository creation for members."
|
||||
@@ -94,6 +96,7 @@ class Test_organization_repository_creation_limited:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].check_metadata.Severity == Severity.high
|
||||
assert "public repositories" in result[0].status_extended
|
||||
assert "repositories of any type" in result[0].status_extended
|
||||
|
||||
@@ -129,6 +132,7 @@ class Test_organization_repository_creation_limited:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].check_metadata.Severity == Severity.high
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Organization {org_name} has disabled repository creation for members."
|
||||
@@ -164,7 +168,410 @@ class Test_organization_repository_creation_limited:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].check_metadata.Severity == Severity.high
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Organization {org_name} has disabled repository creation for members."
|
||||
)
|
||||
|
||||
def test_repository_creation_type_none_with_private_enabled(self):
|
||||
organization_client = mock.MagicMock
|
||||
org_name = "test-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=org_name,
|
||||
mfa_required=None,
|
||||
members_can_create_repositories=True,
|
||||
members_allowed_repository_creation_type="none",
|
||||
members_can_create_private_repositories=True,
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].check_metadata.Severity == Severity.low
|
||||
assert "repositories of any type" not in result[0].status_extended
|
||||
assert "private repositories" in result[0].status_extended
|
||||
assert (
|
||||
"Public repository creation is disabled." in result[0].status_extended
|
||||
)
|
||||
|
||||
def test_repository_creation_public_flag_only(self):
|
||||
organization_client = mock.MagicMock
|
||||
org_name = "test-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=org_name,
|
||||
mfa_required=None,
|
||||
members_can_create_public_repositories=True,
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].check_metadata.Severity == Severity.high
|
||||
|
||||
def test_repository_creation_private_only(self):
|
||||
organization_client = mock.MagicMock
|
||||
org_name = "test-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=org_name,
|
||||
mfa_required=None,
|
||||
members_can_create_repositories=True,
|
||||
members_can_create_public_repositories=False,
|
||||
members_can_create_private_repositories=True,
|
||||
members_can_create_internal_repositories=False,
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].check_metadata.Severity == Severity.low
|
||||
assert (
|
||||
"Public repository creation is disabled." in result[0].status_extended
|
||||
)
|
||||
assert "repositories of any type" not in result[0].status_extended
|
||||
|
||||
def test_repository_creation_public_flag_unknown_is_not_downgraded(self):
|
||||
organization_client = mock.MagicMock
|
||||
org_name = "test-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=org_name,
|
||||
mfa_required=None,
|
||||
members_can_create_private_repositories=True,
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].check_metadata.Severity == Severity.high
|
||||
assert (
|
||||
"Public repository creation is disabled."
|
||||
not in result[0].status_extended
|
||||
)
|
||||
|
||||
def test_repository_creation_internal_only(self):
|
||||
organization_client = mock.MagicMock
|
||||
org_name = "test-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=org_name,
|
||||
mfa_required=None,
|
||||
members_can_create_public_repositories=False,
|
||||
members_can_create_private_repositories=False,
|
||||
members_can_create_internal_repositories=True,
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].check_metadata.Severity == Severity.low
|
||||
assert (
|
||||
"Public repository creation is disabled." in result[0].status_extended
|
||||
)
|
||||
|
||||
def test_repository_creation_private_and_internal(self):
|
||||
organization_client = mock.MagicMock
|
||||
org_name = "test-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=org_name,
|
||||
mfa_required=None,
|
||||
members_can_create_public_repositories=False,
|
||||
members_can_create_private_repositories=True,
|
||||
members_can_create_internal_repositories=True,
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].check_metadata.Severity == Severity.low
|
||||
assert (
|
||||
"Public repository creation is disabled." in result[0].status_extended
|
||||
)
|
||||
|
||||
def test_repository_creation_type_all(self):
|
||||
organization_client = mock.MagicMock
|
||||
org_name = "test-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=org_name,
|
||||
mfa_required=None,
|
||||
members_allowed_repository_creation_type="all",
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].check_metadata.Severity == Severity.high
|
||||
|
||||
def test_repository_creation_type_private(self):
|
||||
organization_client = mock.MagicMock
|
||||
org_name = "test-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=org_name,
|
||||
mfa_required=None,
|
||||
members_allowed_repository_creation_type="private",
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].check_metadata.Severity == Severity.low
|
||||
assert (
|
||||
"Public repository creation is disabled." in result[0].status_extended
|
||||
)
|
||||
|
||||
def test_repository_creation_type_selected_is_undeterminable(self):
|
||||
organization_client = mock.MagicMock
|
||||
org_name = "test-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=org_name,
|
||||
mfa_required=None,
|
||||
members_allowed_repository_creation_type="selected",
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].check_metadata.Severity == Severity.high
|
||||
|
||||
def test_repository_creation_global_flag_only(self):
|
||||
organization_client = mock.MagicMock
|
||||
org_name = "test-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=org_name,
|
||||
mfa_required=None,
|
||||
members_can_create_repositories=True,
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].check_metadata.Severity == Severity.high
|
||||
|
||||
def test_repository_creation_severity_does_not_leak_between_organizations(self):
|
||||
organization_client = mock.MagicMock
|
||||
public_org_name = "public-organization"
|
||||
private_org_name = "private-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=public_org_name,
|
||||
mfa_required=None,
|
||||
members_can_create_public_repositories=True,
|
||||
),
|
||||
2: Org(
|
||||
id=2,
|
||||
name=private_org_name,
|
||||
mfa_required=None,
|
||||
members_can_create_public_repositories=False,
|
||||
members_can_create_private_repositories=True,
|
||||
members_can_create_internal_repositories=False,
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 2
|
||||
|
||||
results_by_name = {r.resource_name: r for r in result}
|
||||
assert results_by_name[public_org_name].status == "FAIL"
|
||||
assert (
|
||||
results_by_name[public_org_name].check_metadata.Severity
|
||||
== Severity.high
|
||||
)
|
||||
assert results_by_name[private_org_name].status == "FAIL"
|
||||
assert (
|
||||
results_by_name[private_org_name].check_metadata.Severity
|
||||
== Severity.low
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user