feat: add GitHub provider documentation and CIS v1.0.0 compliance (#6116)

Co-authored-by: MrCloudSec <hello@mistercloudsec.com>
Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com>
This commit is contained in:
Hugo Pereira Brito
2025-05-14 10:47:33 +02:00
committed by GitHub
parent 484a773f5b
commit a765c1543e
11 changed files with 2929 additions and 3 deletions

View File

@@ -466,3 +466,19 @@ The required modules are:
- [ExchangeOnlineManagement](https://www.powershellgallery.com/packages/ExchangeOnlineManagement/3.6.0): Minimum version 3.6.0. Required for several checks across Exchange, Defender, and Purview.
- [MicrosoftTeams](https://www.powershellgallery.com/packages/MicrosoftTeams/6.6.0): Minimum version 6.6.0. Required for all Teams checks.
## GitHub
### Authentication
Prowler supports multiple methods to [authenticate with GitHub](https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api). These include:
- **Personal Access Token (PAT)**
- **OAuth App Token**
- **GitHub App Credentials**
This flexibility allows you to scan and analyze your GitHub account, including repositories, organizations, and applications, using the method that best suits your use case.
The provided credentials must have the appropriate permissions to perform all the required checks.
???+ note
GitHub App Credentials support less checks than other authentication methods.

View File

@@ -565,6 +565,7 @@ kubectl logs prowler-XXXXX --namespace prowler-ns
???+ note
By default, `prowler` will scan all namespaces in your active Kubernetes context. Use the flag `--context` to specify the context to be scanned and `--namespaces` to specify the namespaces to be scanned.
#### Microsoft 365
With M365 you need to specify which auth method is going to be used:
@@ -587,5 +588,29 @@ prowler m365 --browser-auth --tenant-id "XXXXXXXX"
See more details about M365 Authentication in [Requirements](getting-started/requirements.md#microsoft-365)
#### GitHub
Prowler allows you to scan your GitHub account, including your repositories, organizations or applications.
There are several supported login methods:
```console
# Personal Access Token (PAT):
prowler github --personal-access-token pat
# OAuth App Token:
prowler github --oauth-app-token oauth_token
# GitHub App Credentials:
prowler github --github-app-id app_id --github-app-key app_key
```
???+ note
If no login method is explicitly provided, Prowler will automatically attempt to authenticate using environment variables in the following order of precedence:
1. `GITHUB_PERSONAL_ACCESS_TOKEN`
2. `OAUTH_APP_TOKEN`
3. `GITHUB_APP_ID` and `GITHUB_APP_KEY`
## Prowler v2 Documentation
For **Prowler v2 Documentation**, please check it out [here](https://github.com/prowler-cloud/prowler/blob/8818f47333a0c1c1a457453c87af0ea5b89a385f/README.md).

View File

@@ -0,0 +1,44 @@
# GitHub Authentication
Prowler supports multiple methods to [authenticate with GitHub](https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api). These include:
- **Personal Access Token (PAT)**
- **OAuth App Token**
- **GitHub App Credentials**
This flexibility allows you to scan and analyze your GitHub account, including repositories, organizations, and applications, using the method that best suits your use case.
## Supported Login Methods
Here are the available login methods and their respective flags:
### Personal Access Token (PAT)
Use this method by providing your personal access token directly.
```console
prowler github --personal-access-token pat
```
### OAuth App Token
Authenticate using an OAuth app token.
```console
prowler github --oauth-app-token oauth_token
```
### GitHub App Credentials
Use GitHub App credentials by specifying the App ID and the private key.
```console
prowler github --github-app-id app_id --github-app-key app_key
```
### Automatic Login Method Detection
If no login method is explicitly provided, Prowler will automatically attempt to authenticate using environment variables in the following order of precedence:
1. `GITHUB_PERSONAL_ACCESS_TOKEN`
2. `OAUTH_APP_TOKEN`
3. `GITHUB_APP_ID` and `GITHUB_APP_KEY`
???+ note
Ensure the corresponding environment variables are set up before running Prowler for automatic detection if you don't plan to specify the login method.

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 GitHub provider documentation and CIS v1.0.0 compliance. [(#6116)](https://github.com/prowler-cloud/prowler/pull/6116)
### Fixed
- Update CIS 4.0 for M365 provider. [(#7699)](https://github.com/prowler-cloud/prowler/pull/7699)

View File

@@ -50,6 +50,7 @@ from prowler.lib.outputs.compliance.aws_well_architected.aws_well_architected im
from prowler.lib.outputs.compliance.cis.cis_aws import AWSCIS
from prowler.lib.outputs.compliance.cis.cis_azure import AzureCIS
from prowler.lib.outputs.compliance.cis.cis_gcp import GCPCIS
from prowler.lib.outputs.compliance.cis.cis_github import GithubCIS
from prowler.lib.outputs.compliance.cis.cis_kubernetes import KubernetesCIS
from prowler.lib.outputs.compliance.cis.cis_m365 import M365CIS
from prowler.lib.outputs.compliance.compliance import display_compliance_table
@@ -787,6 +788,36 @@ def prowler():
generated_outputs["compliance"].append(generic_compliance)
generic_compliance.batch_write_data_to_file()
elif provider == "github":
for compliance_name in input_compliance_frameworks:
if compliance_name.startswith("cis_"):
# Generate CIS Finding Object
filename = (
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
cis = GithubCIS(
findings=finding_outputs,
compliance=bulk_compliance_frameworks[compliance_name],
create_file_descriptor=True,
file_path=filename,
)
generated_outputs["compliance"].append(cis)
cis.batch_write_data_to_file()
else:
filename = (
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
generic_compliance = GenericCompliance(
findings=finding_outputs,
compliance=bulk_compliance_frameworks[compliance_name],
create_file_descriptor=True,
file_path=filename,
)
generated_outputs["compliance"].append(generic_compliance)
generic_compliance.batch_write_data_to_file()
# AWS Security Hub Integration
if provider == "aws":
# Send output to S3 if needed (-B / -D) for all the output formats

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,7 @@ Available Cloud Providers:
azure Azure Provider
gcp GCP Provider
kubernetes Kubernetes Provider
github GitHub Provider
m365 Microsoft 365 Provider
nhn NHN Provider (Unofficial)

View File

@@ -0,0 +1,101 @@
from datetime import datetime
from prowler.lib.check.compliance_models import Compliance
from prowler.lib.outputs.compliance.cis.models import GithubCISModel
from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput
from prowler.lib.outputs.finding import Finding
class GithubCIS(ComplianceOutput):
"""
This class represents the GitHub CIS 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 GitHub CIS compliance format.
"""
def transform(
self,
findings: list[Finding],
compliance: Compliance,
compliance_name: str,
) -> None:
"""
Transforms a list of findings into GitHub CIS 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 = GithubCISModel(
Provider=finding.provider,
Description=compliance.Description,
Account_Id=finding.account_uid,
Account_Name=finding.account_name,
AssessmentDate=str(finding.timestamp),
Requirements_Id=requirement.Id,
Requirements_Description=requirement.Description,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_Profile=attribute.Profile,
Requirements_Attributes_AssessmentStatus=attribute.AssessmentStatus,
Requirements_Attributes_Description=attribute.Description,
Requirements_Attributes_RationaleStatement=attribute.RationaleStatement,
Requirements_Attributes_ImpactStatement=attribute.ImpactStatement,
Requirements_Attributes_RemediationProcedure=attribute.RemediationProcedure,
Requirements_Attributes_AuditProcedure=attribute.AuditProcedure,
Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation,
Requirements_Attributes_References=attribute.References,
Requirements_Attributes_DefaultValue=attribute.DefaultValue,
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 = GithubCISModel(
Provider=compliance.Provider.lower(),
Description=compliance.Description,
Account_Id="",
Account_Name="",
AssessmentDate=str(datetime.now()),
Requirements_Id=requirement.Id,
Requirements_Description=requirement.Description,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_Profile=attribute.Profile,
Requirements_Attributes_AssessmentStatus=attribute.AssessmentStatus,
Requirements_Attributes_Description=attribute.Description,
Requirements_Attributes_RationaleStatement=attribute.RationaleStatement,
Requirements_Attributes_ImpactStatement=attribute.ImpactStatement,
Requirements_Attributes_RemediationProcedure=attribute.RemediationProcedure,
Requirements_Attributes_AuditProcedure=attribute.AuditProcedure,
Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation,
Requirements_Attributes_References=attribute.References,
Requirements_Attributes_DefaultValue=attribute.DefaultValue,
Status="MANUAL",
StatusExtended="Manual check",
ResourceId="manual_check",
ResourceName="Manual check",
CheckId="manual",
Muted=False,
)
self._data.append(compliance_row)

View File

@@ -163,6 +163,37 @@ class KubernetesCISModel(BaseModel):
Muted: bool
class GithubCISModel(BaseModel):
"""
GithubCISModel generates a finding's output in Github CIS Compliance format.
"""
Provider: str
Description: str
Account_Name: str
Account_Id: str
AssessmentDate: str
Requirements_Id: str
Requirements_Description: str
Requirements_Attributes_Section: str
Requirements_Attributes_Profile: str
Requirements_Attributes_AssessmentStatus: str
Requirements_Attributes_Description: str
Requirements_Attributes_RationaleStatement: str
Requirements_Attributes_ImpactStatement: str
Requirements_Attributes_RemediationProcedure: str
Requirements_Attributes_AuditProcedure: str
Requirements_Attributes_AdditionalInformation: str
Requirements_Attributes_References: str
Requirements_Attributes_DefaultValue: str
Status: str
StatusExtended: str
ResourceId: str
ResourceName: str
CheckId: str
Muted: bool
# TODO: Create a parent class for the common fields of CIS and have the specific classes from each provider to inherit from it.
# It is not done yet because it is needed to respect the current order of the fields in the output file.

View File

@@ -330,3 +330,55 @@ class TestCompliance:
"CIS-2.0": ["2.1.3"],
"CIS-2.1": ["2.1.3"],
}
def test_get_check_compliance_github(self):
check_compliance = [
Compliance(
Framework="CIS",
Provider="Github",
Version="1.0",
Description="This document provides prescriptive guidance for establishing a secure configuration posture for securing GitHub.",
Requirements=[
Compliance_Requirement(
Checks=[],
Id="1.1.11",
Description="Ensure all open comments are resolved before allowing code change merging",
Attributes=[
CIS_Requirement_Attribute(
Section="1.1",
Profile="Level 2",
AssessmentStatus="Manual",
Description='Organizations should enforce a "no open comments" policy before allowing code change merging.',
RationaleStatement="In an open code change proposal, reviewers can leave comments containing their questions and suggestions. These comments can also include potential bugs and security issues. Requiring all comments on a code change proposal to be resolved before it can be merged ensures that every concern is properly addressed or acknowledged before the new code changes are introduced to the code base.",
ImpactStatement="Code change proposals containing open comments would not be able to be merged into the code base.",
RemediationProcedure='For each code repository in use, require open comments to be resolved before the relevant code change can be merged by performing the following:\n \n\n 1. On GitHub.com, navigate to the main page of the repository.\n 2. Under your repository name, click **Settings**.\n 3. In the "Code and automation" section of the sidebar, click **Branches**.\n 4. Next to "Branch protection rules", verify that there is at least one rule for your main branch. If there is, click **Edit** to its right. If there isn\'t, click **Add rule**.\n 5. If you add the rule, under "Branch name pattern", type the branch name or pattern you want to protect.\n 6. Select **Require conversation resolution before merging**.\n 7. Click **Create** or **Save changes**.',
AuditProcedure='For every code repository in use, verify that each merged code change does not contain open, unattended comments by performing the following:\n \n\n 1. On GitHub.com, navigate to the main page of the repository.\n 2. Under your repository name, click **Settings**.\n 3. In the "Code and automation" section of the sidebar, click **Branches**.\n 4. Next to "Branch protection rules", verify that there is at least one rule for your main branch. If there is, click **Edit** to its right. If there isn\'t, you are not compliant.\n 5. Ensure that **Require conversation resolution before merging** is checked.',
AdditionalInformation="",
References="",
)
],
)
],
)
]
finding = Check_Report(
metadata=load_check_metadata(
f"{path.dirname(path.realpath(__file__))}/../fixtures/metadata.json"
).json(),
resource={},
)
finding.resource_details = "Test resource details"
finding.resource_id = "test-resource"
finding.resource_arn = "test-arn"
finding.region = "eu-west-1"
finding.status = "PASS"
finding.status_extended = "This is a test"
bulk_checks_metadata = {}
bulk_checks_metadata["iam_user_accesskey_unused"] = mock.MagicMock()
bulk_checks_metadata["iam_user_accesskey_unused"].Compliance = check_compliance
assert get_check_compliance(finding, "github", bulk_checks_metadata) == {
"CIS-1.0": ["1.1.11"],
}

View File

@@ -0,0 +1,40 @@
import csv
import json
import sys
# Convert a CSV file following the CIS 1.0 GitHub benchmark into a Prowler v3.0 Compliance JSON file
# CSV fields:
# Id, Title,Checks,Attributes_Section,Attributes_Level,Attributes_AssessmentStatus,Attributes_Description,Attributes_RationalStatement,Attributes_ImpactStatement,Attributes_RemediationProcedure,Attributes_AuditProcedure,Attributes_AdditionalInformation,Attributes_References
# get the CSV filename to convert from
file_name = sys.argv[1]
# read the CSV file rows and use the column fields to form the Prowler compliance JSON file 'cis_1.0_github.json'
output = {"Framework": "CIS-GitHub", "Version": "1.5", "Requirements": []}
with open(file_name, newline="", encoding="utf-8") as f:
reader = csv.reader(f, delimiter=",")
for row in reader:
attribute = {
"Section": row[3],
"Profile": row[4],
"AssessmentStatus": row[5],
"Description": row[6],
"RationaleStatement": row[7],
"ImpactStatement": row[8],
"RemediationProcedure": row[9],
"AuditProcedure": row[10],
"AdditionalInformation": row[11],
"References": row[12],
}
output["Requirements"].append(
{
"Id": row[0],
"Description": row[1],
"Checks": list(map(str.strip, row[2].split(","))),
"Attributes": [attribute],
}
)
# Write the output Prowler compliance JSON file 'cis_1.0_github.json' locally
with open("cis_1.0_github.json", "w", encoding="utf-8") as outfile:
json.dump(output, outfile, indent=4, ensure_ascii=False)