feat(aws): add new check for Codebuild projects visibility (#8127)

Co-authored-by: MrCloudSec <hello@mistercloudsec.com>
This commit is contained in:
Neil Millard
2025-07-02 10:20:15 +01:00
committed by GitHub
parent f78a29206c
commit 965111245a
8 changed files with 238 additions and 0 deletions

View File

@@ -44,6 +44,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `monitor_alert_service_health_exists` check for Azure provider [(#8067)](https://github.com/prowler-cloud/prowler/pull/8067)
- Replace `Domain.Read.All` with `Directory.Read.All` in Azure and M365 docs [(#8075)](https://github.com/prowler-cloud/prowler/pull/8075)
- Refactor IaC provider to use Checkov as Python library [(#8093)](https://github.com/prowler-cloud/prowler/pull/8093)
- New check `codebuild_project_not_publicly_accessible` for AWS provider [(#8127)](https://github.com/prowler-cloud/prowler/pull/8127)
### Fixed
- Consolidate Azure Storage file service properties to the account level, improving the accuracy of the `storage_ensure_file_shares_soft_delete_is_enabled` check [(#8087)](https://github.com/prowler-cloud/prowler/pull/8087)

View File

@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "codebuild_project_not_publicly_accessible",
"CheckTitle": "Ensure AWS CodeBuild projects are not public",
"CheckType": [],
"ServiceName": "codebuild",
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:codebuild:region:account-id:project:project-name",
"Severity": "high",
"ResourceType": "AwsCodeBuildProject",
"Description": "Check for CodeBuild projects ensuring that the project visibility is appropriate",
"Risk": "Public CodeBuild Project ensures all build logs and artifacts are available to the public. Environment variables, source code, and other sensitive information may have been output to the build logs and artifacts. You must be careful about what information is output to the build logs.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"NativeIaC": "",
"Terraform": "",
"CLI": "aws codebuild update-project --name <project-name> --project-visibility PRIVATE",
"Other": ""
},
"Recommendation": {
"Text": "Ensure that all CodeBuild projects are private to avoid fact gathering about builds from an Attacker.",
"Url": "https://docs.aws.amazon.com/codebuild/latest/userguide/public-builds.html"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,26 @@
from typing import List
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.codebuild.codebuild_client import codebuild_client
class codebuild_project_not_publicly_accessible(Check):
def execute(self) -> List[Check_Report_AWS]:
findings = []
projects = codebuild_client.projects
for arn, project in projects.items():
report = Check_Report_AWS(self.metadata(), resource=project)
report.resource_id = project.name
report.resource_arn = arn
report.region = project.region
report.status = "FAIL"
report.status_extended = f"CodeBuild project {project.name} is public."
if project.project_visibility == "PRIVATE":
report.status = "PASS"
report.status_extended = f"CodeBuild project {project.name} is private."
findings.append(report)
return findings

View File

@@ -121,6 +121,7 @@ class Codebuild(AWSService):
)
project.tags = project_info.get("tags", [])
project.service_role_arn = project_info.get("serviceRole", "")
project.project_visibility = project_info.get("projectVisibility", "")
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -222,6 +223,7 @@ class Project(BaseModel):
s3_logs: Optional[s3Logs]
cloudwatch_logs: Optional[CloudWatchLogs]
tags: Optional[list]
project_visibility: Optional[str] = None
class ExportConfig(BaseModel):

View File

@@ -0,0 +1,176 @@
from unittest import mock
from prowler.providers.aws.services.codebuild.codebuild_service import Project
AWS_REGION = "eu-west-1"
AWS_ACCOUNT_NUMBER = "123456789012"
class Test_codebuild_project_not_publicly_accessible:
def test_project_public(self):
codebuild_client = mock.MagicMock
project_name = "test-project"
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
codebuild_client.projects = {
project_arn: Project(
name=project_name,
arn=project_arn,
region="eu-west-1",
project_visibility="PUBLIC",
tags=[],
)
}
with (
mock.patch(
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
codebuild_client,
),
mock.patch(
"prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible.codebuild_client",
codebuild_client,
),
):
from prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible import (
codebuild_project_not_publicly_accessible,
)
check = codebuild_project_not_publicly_accessible()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"CodeBuild project {project_name} is public."
)
assert result[0].resource_id == project_name
assert result[0].resource_arn == project_arn
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION
def test_project_private(self):
codebuild_client = mock.MagicMock
project_name = "test-project"
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
codebuild_client.projects = {
project_arn: Project(
name=project_name,
arn=project_arn,
region="eu-west-1",
project_visibility="PRIVATE",
tags=[],
)
}
with (
mock.patch(
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
codebuild_client,
),
mock.patch(
"prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible.codebuild_client",
codebuild_client,
),
):
from prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible import (
codebuild_project_not_publicly_accessible,
)
check = codebuild_project_not_publicly_accessible()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"CodeBuild project {project_name} is private."
)
assert result[0].resource_id == project_name
assert result[0].resource_arn == project_arn
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION
def test_project_no_visibility_set(self):
codebuild_client = mock.MagicMock
project_name = "test-project"
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
codebuild_client.projects = {
project_arn: Project(
name=project_name,
arn=project_arn,
region="eu-west-1",
project_visibility=None,
tags=[],
)
}
with (
mock.patch(
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
codebuild_client,
),
mock.patch(
"prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible.codebuild_client",
codebuild_client,
),
):
from prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible import (
codebuild_project_not_publicly_accessible,
)
check = codebuild_project_not_publicly_accessible()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"CodeBuild project {project_name} is public."
)
assert result[0].resource_id == project_name
assert result[0].resource_arn == project_arn
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION
def test_project_empty_visibility(self):
codebuild_client = mock.MagicMock
project_name = "test-project"
project_arn = f"arn:aws:codebuild:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:project/{project_name}"
codebuild_client.projects = {
project_arn: Project(
name=project_name,
arn=project_arn,
region="eu-west-1",
project_visibility="",
tags=[],
)
}
with (
mock.patch(
"prowler.providers.aws.services.codebuild.codebuild_service.Codebuild",
codebuild_client,
),
mock.patch(
"prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible.codebuild_client",
codebuild_client,
),
):
from prowler.providers.aws.services.codebuild.codebuild_project_not_publicly_accessible.codebuild_project_not_publicly_accessible import (
codebuild_project_not_publicly_accessible,
)
check = codebuild_project_not_publicly_accessible()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"CodeBuild project {project_name} is public."
)
assert result[0].resource_id == project_name
assert result[0].resource_arn == project_arn
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION

View File

@@ -28,6 +28,7 @@ build_id = "test:93f838a7-cd20-48ae-90e5-c10fbbc78ca6"
last_invoked_time = datetime.now() - timedelta(days=2)
bitbucket_url = "https://bitbucket.org/example/repo.git"
secondary_bitbucket_url = "https://bitbucket.org/example/secondary-repo.git"
project_visibility = "PRIVATE"
report_group_arn = f"arn:{AWS_COMMERCIAL_PARTITION}:codebuild:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:report-group/{project_name}"
@@ -71,6 +72,7 @@ def mock_make_api_call(self, operation_name, kwarg):
},
},
"tags": [{"key": "Name", "value": project_name}],
"projectVisibility": project_visibility,
}
]
}
@@ -152,6 +154,7 @@ class Test_Codebuild_Service:
)
assert codebuild.projects[project_arn].tags[0]["key"] == "Name"
assert codebuild.projects[project_arn].tags[0]["value"] == project_name
assert codebuild.projects[project_arn].project_visibility == project_visibility
# Asserttions related with report groups
assert len(codebuild.report_groups) == 1
assert isinstance(codebuild.report_groups, dict)