From a82b2d1d77b1e65c2f70454ad860ec74f9168c57 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 23 Dec 2024 11:38:30 +0000 Subject: [PATCH 1/3] feat: added check logic and metadata --- .../__init__.py | 0 ...ization_members_mfa_required.metadata.json | 30 +++++++++++++++ .../organization_members_mfa_required.py | 37 +++++++++++++++++++ .../organization/organization_service.py | 9 +++++ 4 files changed, 76 insertions(+) create mode 100644 prowler/providers/github/services/organization/organization_members_mfa_required/__init__.py create mode 100644 prowler/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required.metadata.json create mode 100644 prowler/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required.py diff --git a/prowler/providers/github/services/organization/organization_members_mfa_required/__init__.py b/prowler/providers/github/services/organization/organization_members_mfa_required/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/prowler/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required.metadata.json b/prowler/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required.metadata.json new file mode 100644 index 00000000000..73d77524366 --- /dev/null +++ b/prowler/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "github", + "CheckID": "organization_members_mfa_required", + "CheckTitle": "Check if organization members are required to have MFA enabled.", + "CheckType": [], + "ServiceName": "organization", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "critical", + "ResourceType": "Other", + "Description": "Ensure that all organization members are required to have multi-factor authentication (MFA) enabled. Enforcing MFA for all organization members helps protect the organization's resources and data from unauthorized access and security breaches.", + "Risk": "Without Multi-Factor Authentication (MFA), user accounts are vulnerable to unauthorized access if their passwords are compromised. This can lead to unauthorized actions such as data theft, malicious code commits, and repository manipulation, potentially compromising the organization's source code and intellectual property.", + "RelatedUrl": "https://docs.github.com/en/organizations/keeping-your-organization-secure/managing-two-factor-authentication-for-your-organization/requiring-two-factor-authentication-in-your-organization", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "https://docs.github.com/en/organizations/keeping-your-organization-secure/managing-two-factor-authentication-for-your-organization", + "Terraform": "" + }, + "Recommendation": { + "Text": "Mandate the use of MFA for all organization members. This significantly enhances account security by adding an additional layer of protection beyond a username and password. MFA ensures that even if a password is compromised, unauthorized access to user accounts and repositories is prevented, safeguarding sensitive data and critical assets.", + "Url": "https://docs.github.com/en/organizations/keeping-your-organization-secure/managing-two-factor-authentication-for-your-organization/preparing-to-require-two-factor-authentication-in-your-organization" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required.py b/prowler/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required.py new file mode 100644 index 00000000000..4ddf5e67289 --- /dev/null +++ b/prowler/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required.py @@ -0,0 +1,37 @@ +from typing import List + +from prowler.lib.check.models import Check, Check_Report_Github +from prowler.providers.github.services.organization.organization_client import ( + organization_client, +) + + +class organization_members_mfa_required(Check): + """Check if organization members are required to have two-factor authentication enabled. + + This class verifies whether each organization requires its members to have two-factor authentication enabled. + """ + + def execute(self) -> List[Check_Report_Github]: + """Execute the Github Organization Members MFA Required check. + + Iterates over all organizations and checks if members are required to have two-factor authentication enabled. + + Returns: + List[Check_Report_Github]: A list of reports for each repository + """ + findings = [] + for org in organization_client.organizations.values(): + report = Check_Report_Github(self.metadata()) + report.resource_id = org.id + report.resource_name = org.name + report.status = "FAIL" + report.status_extended = f"Organization {org.name} does not require members to have two-factor authentication enabled." + + if org.mfa_required: + report.status = "PASS" + report.status_extended = f"Organization {org.name} does require members to have two-factor authentication enabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/github/services/organization/organization_service.py b/prowler/providers/github/services/organization/organization_service.py index 64c2e7c0d83..ef88aecd9cd 100644 --- a/prowler/providers/github/services/organization/organization_service.py +++ b/prowler/providers/github/services/organization/organization_service.py @@ -15,9 +15,17 @@ def _list_organizations(self): try: for client in self.clients: for org in client.get_user().get_orgs(): + try: + require_mfa = org.two_factor_requirement_enabled is not None + except Exception as error: + require_mfa = False + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) organizations[org.id] = Org( id=org.id, name=org.login, + mfa_required=require_mfa, ) except Exception as error: logger.error( @@ -31,3 +39,4 @@ class Org(BaseModel): id: int name: str + mfa_required: bool = False From 6fa3c8ab90f1005156f95920a88e103f346a1624 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 23 Dec 2024 11:38:59 +0000 Subject: [PATCH 2/3] feat: added testing --- .../organization_members_mfa_required_test.py | 91 +++++++++++++++++++ .../organization/organization_service_test.py | 2 + 2 files changed, 93 insertions(+) create mode 100644 tests/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required_test.py diff --git a/tests/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required_test.py b/tests/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required_test.py new file mode 100644 index 00000000000..1c3c1baac18 --- /dev/null +++ b/tests/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required_test.py @@ -0,0 +1,91 @@ +from unittest import mock + +from prowler.providers.github.services.organization.organization_service import Org +from tests.providers.github.github_fixtures import set_mocked_github_provider + + +class Test_organization_members_mfa_required: + def test_no_organizations(self): + organization_client = mock.MagicMock + organization_client.organizations = {} + + 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_members_mfa_required.organization_members_mfa_required.organization_client", + new=organization_client, + ): + from prowler.providers.github.services.organization.organization_members_mfa_required.organization_members_mfa_required import ( + organization_members_mfa_required, + ) + + check = organization_members_mfa_required() + result = check.execute() + assert len(result) == 0 + + def test_organization_mfa_disabled(self): + organization_client = mock.MagicMock + org_name = "test-organization" + organization_client.organizations = { + 1: Org( + id=1, + name=org_name, + mfa_required=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_members_mfa_required.organization_members_mfa_required.organization_client", + new=organization_client, + ): + from prowler.providers.github.services.organization.organization_members_mfa_required.organization_members_mfa_required import ( + organization_members_mfa_required, + ) + + check = organization_members_mfa_required() + result = check.execute() + assert len(result) == 1 + assert result[0].resource_id == 1 + assert result[0].resource_name == "test-organization" + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Organization {org_name} does not require members to have two-factor authentication enabled." + ) + + def test_one_organization_securitymd(self): + organization_client = mock.MagicMock + org_name = "test-organization" + organization_client.organizations = { + 1: Org( + id=1, + name=org_name, + mfa_required=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_members_mfa_required.organization_members_mfa_required.organization_client", + new=organization_client, + ): + from prowler.providers.github.services.organization.organization_members_mfa_required.organization_members_mfa_required import ( + organization_members_mfa_required, + ) + + check = organization_members_mfa_required() + result = check.execute() + assert len(result) == 1 + assert result[0].resource_id == 1 + assert result[0].resource_name == "test-organization" + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Organization {org_name} does require members to have two-factor authentication enabled." + ) diff --git a/tests/providers/github/services/organization/organization_service_test.py b/tests/providers/github/services/organization/organization_service_test.py index 02dabf80ff3..77208416987 100644 --- a/tests/providers/github/services/organization/organization_service_test.py +++ b/tests/providers/github/services/organization/organization_service_test.py @@ -12,6 +12,7 @@ def mock_list_organizations(_): 1: Org( id=1, name="test-organization", + mfa_required=True, ), } @@ -33,3 +34,4 @@ def test_list_organizations(self): repository_service = Organization(set_mocked_github_provider()) assert len(repository_service.organizations) == 1 assert repository_service.organizations[1].name == "test-organization" + assert repository_service.organizations[1].mfa_required From 4b739174b86b560007fcb8f6f4a5c91bcb77ee52 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Thu, 16 Jan 2025 12:55:36 +0100 Subject: [PATCH 3/3] feat: add resource metadata --- .../organization_members_mfa_required.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prowler/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required.py b/prowler/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required.py index 4ddf5e67289..8426fe8135b 100644 --- a/prowler/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required.py +++ b/prowler/providers/github/services/organization/organization_members_mfa_required/organization_members_mfa_required.py @@ -22,9 +22,9 @@ def execute(self) -> List[Check_Report_Github]: """ findings = [] for org in organization_client.organizations.values(): - report = Check_Report_Github(self.metadata()) - report.resource_id = org.id - report.resource_name = org.name + report = Check_Report_Github( + metadata=self.metadata(), resource_metadata=org + ) report.status = "FAIL" report.status_extended = f"Organization {org.name} does not require members to have two-factor authentication enabled."