Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(codebuild): add new check codebuild_report_group_export_encrypted #5384

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
128adbb
feat(codebuild): add s3Logs to Project
puchy22 Oct 10, 2024
751de04
test(codebuild): test S3Logs attribute
puchy22 Oct 10, 2024
0d43d2a
feat(codebuild): add new check to ensure S3 Logs are encrypted at rest
puchy22 Oct 10, 2024
a84cb7d
test(codebuild): test check with logs encrypted and not encrypted
puchy22 Oct 10, 2024
9310169
fix(codebuild): fix tests
puchy22 Oct 10, 2024
a8461d4
chore(codebuild): not check if logs are enabled
puchy22 Oct 10, 2024
bcd07e6
feat(codebuild): add CloudWatchLogs to Projects
puchy22 Oct 10, 2024
bfd3b38
feat(codebuild): add new check to ensure logging is enabled
puchy22 Oct 10, 2024
8ff1bff
test(codebuild): test check with logging enabled and disabled
puchy22 Oct 10, 2024
ba091f7
test(codebuild): test cloudwatch_logs attribute
puchy22 Oct 10, 2024
613c9d6
chore(codebuild): add tags to report
puchy22 Oct 10, 2024
665b231
chore(codebuild): add other remediation
puchy22 Oct 10, 2024
5f843e6
feat(permissions): add needed permissions for list CodeBuild report g…
puchy22 Oct 11, 2024
a6d1a0a
feat(codebuild): add report_groups
puchy22 Oct 11, 2024
f33b977
test(codebuild): test new report_groups attributes
puchy22 Oct 11, 2024
d68ba60
feat(codebuild): new check to ensure repot group exports are encrypted
puchy22 Oct 11, 2024
dd4a5dc
test(codebuild): test new check with encrypted and unencrypted exports
puchy22 Oct 11, 2024
7554ee9
chore(codebuild): change metadata fields order
puchy22 Oct 14, 2024
d3e085f
Merge branch 'PRWLR-4460-ensure-code-build-s-3-logs-are-encrypted' in…
puchy22 Oct 14, 2024
080dc79
chore(codebuild): change metadata fields order
puchy22 Oct 14, 2024
4472a87
Merge branch 'PRWLR-4462-ensure-code-build-project-environments-have-…
puchy22 Oct 14, 2024
c41a71f
chore(codebuild): change metadata fields order
puchy22 Oct 14, 2024
bdc7182
Merge branch 'master' into PRWLR-4803-code-build-report-group-exports…
sergargar Oct 15, 2024
14c43a1
chore: revision
sergargar Oct 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions permissions/create_role_to_assume_cfn.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Resources:
- 'cloudtrail:GetInsightSelectors'
- 'codeartifact:List*'
- 'codebuild:BatchGet*'
- 'codebuild:ListReportGroups'
- 'cognito-idp:GetUserPoolMfaConfig'
- 'dlm:Get*'
- 'drs:Describe*'
Expand Down
1 change: 1 addition & 0 deletions permissions/prowler-additions-policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"cloudtrail:GetInsightSelectors",
"codeartifact:List*",
"codebuild:BatchGet*",
"codebuild:ListReportGroups",
"cognito-idp:GetUserPoolMfaConfig",
"dlm:Get*",
"drs:Describe*",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"Provider": "aws",
"CheckID": "codebuild_report_group_export_encrypted",
"CheckTitle": "CodeBuild report group exports are encrypted at rest",
"CheckType": [],
"ServiceName": "codebuild",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "Other",
"Description": "Ensure that CodeBuild report group exports are encrypted at rest.",
"Risk": "If CodeBuild report group exports are not encrypted, sensitive data could be exposed to unauthorized access.",
"RelatedUrl": "https://docs.aws.amazon.com/codebuild/latest/userguide/report-group-export-settings.html",
"Remediation": {
"Code": {
"CLI": "aws codebuild update-report-group --arn <report-group-arn> --export-config \"exportConfigType=S3, s3Destination={bucket=, encryptionDisabled=true}\"",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/codebuild-controls.html#codebuild-7",
"Terraform": ""
},
"Recommendation": {
"Text": "Configure CodeBuild report group exports to use encryption at rest. This can be done by specifying a KMS key ID when creating or updating the report group.",
"Url": "https://docs.aws.amazon.com/codebuild/latest/userguide/report-group-export-settings.html"
}
},
"Categories": [
"encryption"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.codebuild.codebuild_client import codebuild_client


class codebuild_report_group_export_encrypted(Check):
def execute(self):
findings = []
for report_group in codebuild_client.report_groups.values():
if report_group.export_config and report_group.export_config.type == "S3":
report = Check_Report_AWS(self.metadata())
report.resource_id = report_group.name
report.resource_arn = report_group.arn
report.region = report_group.region
report.resource_tags = report_group.tags
report.status = "PASS"
report.status_extended = f"CodeBuild report group {report_group.name} exports are encrypted at {report_group.export_config.bucket_location} with KMS key {report_group.export_config.encryption_key}."

if not report_group.export_config.encrypted:
report.status = "FAIL"
report.status_extended = f"CodeBuild report group {report_group.name} exports are not encrypted at {report_group.export_config.bucket_location}."

findings.append(report)

return findings
75 changes: 75 additions & 0 deletions prowler/providers/aws/services/codebuild/codebuild_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
self.__threading_call__(self._list_builds_for_project, self.projects.values())
self.__threading_call__(self._batch_get_builds, self.projects.values())
self.__threading_call__(self._batch_get_projects, self.projects.values())
self.report_groups = {}
self.__threading_call__(self._list_report_groups)
self.__threading_call__(
self._batch_get_report_groups, self.report_groups.values()
)

def _list_projects(self, regional_client):
logger.info("Codebuild - Listing projects...")
Expand Down Expand Up @@ -119,6 +124,60 @@
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)

def _list_report_groups(self, regional_client):
logger.info("Codebuild - Listing report groups...")
try:
list_report_groups_paginator = regional_client.get_paginator(
"list_report_groups"
)
for page in list_report_groups_paginator.paginate():
for report_group_arn in page["reportGroups"]:
if not self.audit_resources or (
is_resource_filtered(report_group_arn, self.audit_resources)
):
self.report_groups[report_group_arn] = ReportGroup(
arn=report_group_arn,
name=report_group_arn.split(":")[-1].split("/")[-1],
region=regional_client.region,
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)

def _batch_get_report_groups(self, report_group):
logger.info("Codebuild - Getting report groups...")
try:
report_group_info = self.regional_clients[
report_group.region
].batch_get_report_groups(reportGroupArns=[report_group.arn])[
"reportGroups"
][
0
]

report_group.status = report_group_info.get("status", "DELETING")

export_config = report_group_info.get("exportConfig", {})
if export_config:
s3_destination = export_config.get("s3Destination", {})
report_group.export_config = ExportConfig(
type=export_config.get("exportConfigType", "NO_EXPORT"),
bucket_location=(
f"s3://{s3_destination.get('bucket', '')}/{s3_destination.get('path', '')}"
if s3_destination.get("bucket", "")
else ""
),
encryption_key=s3_destination.get("encryptionKey", ""),
encrypted=(not s3_destination.get("encryptionDisabled", True)),
)

report_group.tags = report_group_info.get("tags", [])
except Exception as error:
logger.error(

Check warning on line 177 in prowler/providers/aws/services/codebuild/codebuild_service.py

View check run for this annotation

Codecov / codecov/patch

prowler/providers/aws/services/codebuild/codebuild_service.py#L176-L177

Added lines #L176 - L177 were not covered by tests
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)


class Build(BaseModel):
id: str
Expand Down Expand Up @@ -160,3 +219,19 @@
s3_logs: Optional[s3Logs]
cloudwatch_logs: Optional[CloudWatchLogs]
tags: Optional[list]


class ExportConfig(BaseModel):
type: str
bucket_location: str
encryption_key: str
encrypted: bool


class ReportGroup(BaseModel):
arn: str
name: str
region: str
status: Optional[str]
export_config: Optional[ExportConfig]
tags: Optional[list]
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
from unittest.mock import patch

import botocore
from moto import mock_aws

from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_EU_WEST_1,
set_mocked_aws_provider,
)

orig = botocore.client.BaseClient._make_api_call


def mock_make_api_call_encrypted(self, operation_name, kwarg):
if operation_name == "ListReportGroups":
return {
"reportGroups": [
f"arn:aws:codebuild:eu-west-1:{AWS_ACCOUNT_NUMBER}:report-group/test-report-group-encrypted"
]
}
elif operation_name == "BatchGetReportGroups":
return {
"reportGroups": [
{
"name": "test-report-group-encrypted",
"arn": f"arn:aws:codebuild:eu-west-1:{AWS_ACCOUNT_NUMBER}:report-group/test-report-group-encrypted",
"exportConfig": {
"exportConfigType": "S3",
"s3Destination": {
"bucket": "test-bucket",
"path": "test-path",
"encryptionKey": f"arn:aws:kms:eu-west-1:{AWS_ACCOUNT_NUMBER}:key/12345678-1234-1234-1234-{AWS_ACCOUNT_NUMBER}",
"encryptionDisabled": False,
},
},
"tags": [{"key": "Name", "value": "test-report-group-encrypted"}],
"status": "ACTIVE",
}
]
}
# If we don't want to patch the API call
return orig(self, operation_name, kwarg)


def mock_make_api_call_not_encrypted(self, operation_name, kwarg):
if operation_name == "ListReportGroups":
return {
"reportGroups": [
f"arn:aws:codebuild:eu-west-1:{AWS_ACCOUNT_NUMBER}:report-group/test-report-group-not-encrypted"
]
}
elif operation_name == "BatchGetReportGroups":
return {
"reportGroups": [
{
"name": "test-report-group-not-encrypted",
"arn": f"arn:aws:codebuild:eu-west-1:{AWS_ACCOUNT_NUMBER}:report-group/test-report-group-not-encrypted",
"exportConfig": {
"exportConfigType": "S3",
"s3Destination": {
"bucket": "test-bucket",
"path": "test-path",
"encryptionKey": "",
"encryptionDisabled": True,
},
},
"tags": [
{"key": "Name", "value": "test-report-group-not-encrypted"}
],
"status": "ACTIVE",
}
]
}
# If we don't want to patch the API call
return orig(self, operation_name, kwarg)


class Test_codebuild_report_group_export_encrypted:
@mock_aws
def test_no_report_groups(self):
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])

from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild

with patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), patch(
"prowler.providers.aws.services.codebuild.codebuild_report_group_export_encrypted.codebuild_report_group_export_encrypted.codebuild_client",
new=Codebuild(aws_provider),
):
from prowler.providers.aws.services.codebuild.codebuild_report_group_export_encrypted.codebuild_report_group_export_encrypted import (
codebuild_report_group_export_encrypted,
)

check = codebuild_report_group_export_encrypted()
result = check.execute()

assert len(result) == 0

@patch(
"botocore.client.BaseClient._make_api_call", new=mock_make_api_call_encrypted
)
@mock_aws
def test_report_group_export_encrypted(self):
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])

from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild

with patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), patch(
"prowler.providers.aws.services.codebuild.codebuild_report_group_export_encrypted.codebuild_report_group_export_encrypted.codebuild_client",
new=Codebuild(aws_provider),
):
from prowler.providers.aws.services.codebuild.codebuild_report_group_export_encrypted.codebuild_report_group_export_encrypted import (
codebuild_report_group_export_encrypted,
)

check = codebuild_report_group_export_encrypted()
result = check.execute()

assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"CodeBuild report group test-report-group-encrypted exports are encrypted at s3://test-bucket/test-path with KMS key arn:aws:kms:eu-west-1:{AWS_ACCOUNT_NUMBER}:key/12345678-1234-1234-1234-{AWS_ACCOUNT_NUMBER}."
)
assert result[0].resource_id == "test-report-group-encrypted"
assert (
result[0].resource_arn
== f"arn:aws:codebuild:eu-west-1:{AWS_ACCOUNT_NUMBER}:report-group/test-report-group-encrypted"
)
assert result[0].resource_tags == [
{"key": "Name", "value": "test-report-group-encrypted"}
]
assert result[0].region == AWS_REGION_EU_WEST_1

@patch(
"botocore.client.BaseClient._make_api_call",
new=mock_make_api_call_not_encrypted,
)
@mock_aws
def test_report_group_export_not_encrypted(self):
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])

from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild

with patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), patch(
"prowler.providers.aws.services.codebuild.codebuild_report_group_export_encrypted.codebuild_report_group_export_encrypted.codebuild_client",
new=Codebuild(aws_provider),
):
from prowler.providers.aws.services.codebuild.codebuild_report_group_export_encrypted.codebuild_report_group_export_encrypted import (
codebuild_report_group_export_encrypted,
)

check = codebuild_report_group_export_encrypted()
result = check.execute()

assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "CodeBuild report group test-report-group-not-encrypted exports are not encrypted at s3://test-bucket/test-path."
)
assert result[0].resource_id == "test-report-group-not-encrypted"
assert (
result[0].resource_arn
== f"arn:aws:codebuild:eu-west-1:{AWS_ACCOUNT_NUMBER}:report-group/test-report-group-not-encrypted"
)
assert result[0].resource_tags == [
{"key": "Name", "value": "test-report-group-not-encrypted"}
]
assert result[0].region == AWS_REGION_EU_WEST_1
Loading