feat(organization): add new check organization_members_mfa_required (#6304)

Co-authored-by: MrCloudSec <hello@mistercloudsec.com>
This commit is contained in:
Hugo Pereira Brito
2025-05-14 13:29:08 +02:00
committed by César Arroba
parent a0e72eab0b
commit f1c165a89d
10 changed files with 207 additions and 2 deletions

View File

@@ -90,7 +90,7 @@ prowler dashboard
| GCP | 78 | 13 | 7 | 3 |
| Azure | 140 | 18 | 7 | 3 |
| Kubernetes | 83 | 7 | 4 | 7 |
| GitHub | 2 | 1 | 1 | 0 |
| GitHub | 3 | 2 | 1 | 0 |
| M365 | 44 | 2 | 2 | 0 |
| NHN (Unofficial) | 6 | 2 | 1 | 0 |

26
poetry.lock generated
View File

@@ -3969,6 +3969,32 @@ cffi = ">=1.4.1"
docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"]
[[package]]
name = "pynacl"
version = "1.5.0"
description = "Python binding to the Networking and Cryptography (NaCl) library"
optional = false
python-versions = ">=3.6"
files = [
{file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"},
{file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"},
{file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"},
{file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"},
{file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"},
{file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"},
{file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"},
{file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"},
{file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"},
{file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"},
]
[package.dependencies]
cffi = ">=1.4.1"
[package.extras]
docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"]
[[package]]
name = "pyparsing"
version = "3.2.3"

View File

@@ -10,6 +10,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Add Prowler ThreatScore for M365 provider. [(#7692)](https://github.com/prowler-cloud/prowler/pull/7692)
- Add GitHub provider. [(#5787)](https://github.com/prowler-cloud/prowler/pull/5787)
- Add `repository_code_changes_multi_approval_requirement` check for GitHub provider. [(#6160)](https://github.com/prowler-cloud/prowler/pull/6160)
- Add `organization_members_mfa_required` check for GitHub provider. [(#6304)](https://github.com/prowler-cloud/prowler/pull/6304)
- Add GitHub provider documentation and CIS v1.0.0 compliance. [(#6116)](https://github.com/prowler-cloud/prowler/pull/6116)
### Fixed

View File

@@ -799,7 +799,6 @@ def prowler():
cis = GithubCIS(
findings=finding_outputs,
compliance=bulk_compliance_frameworks[compliance_name],
create_file_descriptor=True,
file_path=filename,
)
generated_outputs["compliance"].append(cis)

View File

@@ -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": "GitHubOrganization",
"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": "",
"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": ""
}

View File

@@ -0,0 +1,36 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportGithub
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[CheckReportGithub]:
"""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[CheckReportGithub]: A list of reports for each repository
"""
findings = []
for org in organization_client.organizations.values():
if org.mfa_required is not None:
report = CheckReportGithub(metadata=self.metadata(), resource=org)
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

View File

@@ -1,3 +1,5 @@
from typing import Optional
from pydantic import BaseModel
from prowler.lib.logger import logger
@@ -15,9 +17,17 @@ class Organization(GithubService):
try:
for client in self.clients:
for org in client.get_user().get_orgs():
try:
require_mfa = org.two_factor_requirement_enabled
except Exception as error:
require_mfa = None
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 +41,4 @@ class Org(BaseModel):
id: int
name: str
mfa_required: Optional[bool] = False

View File

@@ -0,0 +1,100 @@
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."
)

View File

@@ -12,6 +12,7 @@ def mock_list_organizations(_):
1: Org(
id=1,
name="test-organization",
mfa_required=True,
),
}
@@ -33,3 +34,4 @@ class Test_Repository_Service:
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