From aac6038565c0bd16923ef35876fcff549ea0e486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Tue, 15 Oct 2024 13:42:45 +0200 Subject: [PATCH] feat(codebuild): add new check `codebuild_project_logging_enabled` (#5365) Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com> --- .../__init__.py | 0 ...uild_project_logging_enabled.metadata.json | 32 ++ .../codebuild_project_logging_enabled.py | 30 ++ .../services/codebuild/codebuild_service.py | 19 ++ .../codebuild_project_logging_enabled_test.py | 281 ++++++++++++++++++ .../codebuild/codebuild_service_test.py | 18 +- 6 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 prowler/providers/aws/services/codebuild/codebuild_project_logging_enabled/__init__.py create mode 100644 prowler/providers/aws/services/codebuild/codebuild_project_logging_enabled/codebuild_project_logging_enabled.metadata.json create mode 100644 prowler/providers/aws/services/codebuild/codebuild_project_logging_enabled/codebuild_project_logging_enabled.py create mode 100644 tests/providers/aws/services/codebuild/codebuild_project_logging_enabled/codebuild_project_logging_enabled_test.py diff --git a/prowler/providers/aws/services/codebuild/codebuild_project_logging_enabled/__init__.py b/prowler/providers/aws/services/codebuild/codebuild_project_logging_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/codebuild/codebuild_project_logging_enabled/codebuild_project_logging_enabled.metadata.json b/prowler/providers/aws/services/codebuild/codebuild_project_logging_enabled/codebuild_project_logging_enabled.metadata.json new file mode 100644 index 0000000000..13c6787fc2 --- /dev/null +++ b/prowler/providers/aws/services/codebuild/codebuild_project_logging_enabled/codebuild_project_logging_enabled.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "codebuild_project_logging_enabled", + "CheckTitle": "Ensure that CodeBuild projects have S3 or CloudWatch logging enabled", + "CheckType": [], + "ServiceName": "codebuild", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsCodeBuildProject", + "Description": "Ensure that CodeBuild projects have S3 or CloudWatch logging enabled.", + "Risk": "Without logging, tracking and investigating security incidents in CodeBuild projects becomes challenging, reducing confidence in threat detections.", + "RelatedUrl": "https://docs.aws.amazon.com/codebuild/latest/userguide/change-project.html#change-project-console-logs", + "Remediation": { + "Code": { + "CLI": "aws codebuild update-project --name --logs-config \"cloudWatchLogs={status=ENABLED},s3Logs={status=ENABLED\"}", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/codebuild-controls.html#codebuild-4", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable logging for CodeBuild projects to capture build events and logs for future analysis and incident response.", + "Url": "https://docs.aws.amazon.com/codebuild/latest/userguide/change-project.html#change-project-console-logs" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/codebuild/codebuild_project_logging_enabled/codebuild_project_logging_enabled.py b/prowler/providers/aws/services/codebuild/codebuild_project_logging_enabled/codebuild_project_logging_enabled.py new file mode 100644 index 0000000000..428282f59c --- /dev/null +++ b/prowler/providers/aws/services/codebuild/codebuild_project_logging_enabled/codebuild_project_logging_enabled.py @@ -0,0 +1,30 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.codebuild.codebuild_client import codebuild_client + + +class codebuild_project_logging_enabled(Check): + def execute(self): + findings = [] + for project in codebuild_client.projects.values(): + report = Check_Report_AWS(self.metadata()) + report.resource_id = project.name + report.resource_arn = project.arn + report.region = project.region + report.resource_tags = project.tags + report.status = "PASS" + + if project.cloudwatch_logs.enabled and project.s3_logs.enabled: + report.status_extended = f"CodeBuild project {project.name} has enabled CloudWartch logs in log group {project.cloudwatch_logs.group_name} and S3 logs in bucket {project.s3_logs.bucket_location}." + elif project.cloudwatch_logs.enabled: + report.status_extended = f"CodeBuild project {project.name} has CloudWatch logging enabled in log group {project.cloudwatch_logs.group_name}." + elif project.s3_logs.enabled: + report.status_extended = f"CodeBuild project {project.name} has S3 logging enabled in bucket {project.s3_logs.bucket_location}." + else: + report.status = "FAIL" + report.status_extended = ( + f"CodeBuild project {project.name} does not have logging enabled." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/codebuild/codebuild_service.py b/prowler/providers/aws/services/codebuild/codebuild_service.py index ba5d1202d4..e4854ccb9e 100644 --- a/prowler/providers/aws/services/codebuild/codebuild_service.py +++ b/prowler/providers/aws/services/codebuild/codebuild_service.py @@ -101,6 +101,18 @@ class Codebuild(AWSService): bucket_location=s3_logs.get("location", ""), encrypted=(not s3_logs.get("encryptionDisabled", False)), ) + cloudwatch_logs = project_info.get("logsConfig", {}).get( + "cloudWatchLogs", {} + ) + project.cloudwatch_logs = CloudWatchLogs( + enabled=( + True + if cloudwatch_logs.get("status", "DISABLED") == "ENABLED" + else False + ), + group_name=cloudwatch_logs.get("groupName", ""), + stream_name=cloudwatch_logs.get("streamName", ""), + ) project.tags = project_info.get("tags", []) except Exception as error: logger.error( @@ -129,6 +141,12 @@ class s3Logs(BaseModel): encrypted: bool +class CloudWatchLogs(BaseModel): + enabled: bool + group_name: str + stream_name: str + + class Project(BaseModel): name: str arn: str @@ -140,4 +158,5 @@ class Project(BaseModel): secondary_sources: Optional[list[Source]] = [] environment_variables: Optional[List[EnvironmentVariable]] s3_logs: Optional[s3Logs] + cloudwatch_logs: Optional[CloudWatchLogs] tags: Optional[list] diff --git a/tests/providers/aws/services/codebuild/codebuild_project_logging_enabled/codebuild_project_logging_enabled_test.py b/tests/providers/aws/services/codebuild/codebuild_project_logging_enabled/codebuild_project_logging_enabled_test.py new file mode 100644 index 0000000000..df1bf4936b --- /dev/null +++ b/tests/providers/aws/services/codebuild/codebuild_project_logging_enabled/codebuild_project_logging_enabled_test.py @@ -0,0 +1,281 @@ +from unittest.mock import patch + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider + + +class Test_codebuild_project_logging_enabled: + @mock_aws + def test_no_projects(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_project_logging_enabled.codebuild_project_logging_enabled.codebuild_client", + new=Codebuild(aws_provider), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_logging_enabled.codebuild_project_logging_enabled import ( + codebuild_project_logging_enabled, + ) + + check = codebuild_project_logging_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_project_cloudwatch_logging_enabled(self): + codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1) + + project_name = "test-project-logging-enabled" + + project_arn = codebuild_client.create_project( + name=project_name, + source={ + "type": "S3", + "location": "test-bucket", + }, + artifacts={ + "type": "NO_ARTIFACTS", + }, + environment={ + "type": "LINUX_CONTAINER", + "image": "aws/codebuild/standard:4.0", + "computeType": "BUILD_GENERAL1_SMALL", + "environmentVariables": [], + }, + serviceRole="arn:aws:iam::123456789012:role/service-role/codebuild-role", + logsConfig={ + "cloudWatchLogs": { + "status": "ENABLED", + "groupName": "cw-test-group", + } + }, + tags=[ + {"key": "Name", "value": "test"}, + ], + )["project"]["arn"] + + 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_project_logging_enabled.codebuild_project_logging_enabled.codebuild_client", + new=Codebuild(aws_provider), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_logging_enabled.codebuild_project_logging_enabled import ( + codebuild_project_logging_enabled, + ) + + check = codebuild_project_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"CodeBuild project {project_name} has CloudWatch logging enabled in log group cw-test-group." + ) + assert result[0].resource_id == project_name + assert result[0].resource_arn == project_arn + assert result[0].resource_tags == [{"key": "Name", "value": "test"}] + assert result[0].region == AWS_REGION_EU_WEST_1 + + @mock_aws + def test_project_s3_logging_enabled(self): + codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1) + + project_name = "test-project-logging-enabled" + + project_arn = codebuild_client.create_project( + name=project_name, + source={ + "type": "S3", + "location": "test-bucket", + }, + artifacts={ + "type": "NO_ARTIFACTS", + }, + environment={ + "type": "LINUX_CONTAINER", + "image": "aws/codebuild/standard:4.0", + "computeType": "BUILD_GENERAL1_SMALL", + "environmentVariables": [], + }, + serviceRole="arn:aws:iam::123456789012:role/service-role/codebuild-role", + logsConfig={ + "s3Logs": { + "status": "ENABLED", + "location": "s3://test-bucket/logs", + } + }, + tags=[ + {"key": "Name", "value": "test"}, + ], + )["project"]["arn"] + + 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_project_logging_enabled.codebuild_project_logging_enabled.codebuild_client", + new=Codebuild(aws_provider), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_logging_enabled.codebuild_project_logging_enabled import ( + codebuild_project_logging_enabled, + ) + + check = codebuild_project_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"CodeBuild project {project_name} has S3 logging enabled in bucket s3://test-bucket/logs." + ) + assert result[0].resource_id == project_name + assert result[0].resource_arn == project_arn + assert result[0].resource_tags == [{"key": "Name", "value": "test"}] + assert result[0].region == AWS_REGION_EU_WEST_1 + + @mock_aws + def test_project_both_logging_enabled(self): + codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1) + + project_name = "test-project-logging-enabled" + + project_arn = codebuild_client.create_project( + name=project_name, + source={ + "type": "S3", + "location": "test-bucket", + }, + artifacts={ + "type": "NO_ARTIFACTS", + }, + environment={ + "type": "LINUX_CONTAINER", + "image": "aws/codebuild/standard:4.0", + "computeType": "BUILD_GENERAL1_SMALL", + "environmentVariables": [], + }, + serviceRole="arn:aws:iam::123456789012:role/service-role/codebuild-role", + logsConfig={ + "cloudWatchLogs": { + "status": "ENABLED", + "groupName": "cw-test-group", + }, + "s3Logs": { + "status": "ENABLED", + "location": "s3://test-bucket/logs", + }, + }, + tags=[ + {"key": "Name", "value": "test"}, + ], + )["project"]["arn"] + + 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_project_logging_enabled.codebuild_project_logging_enabled.codebuild_client", + new=Codebuild(aws_provider), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_logging_enabled.codebuild_project_logging_enabled import ( + codebuild_project_logging_enabled, + ) + + check = codebuild_project_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"CodeBuild project {project_name} has enabled CloudWartch logs in log group cw-test-group and S3 logs in bucket s3://test-bucket/logs." + ) + assert result[0].resource_id == project_name + assert result[0].resource_arn == project_arn + assert result[0].resource_tags == [{"key": "Name", "value": "test"}] + assert result[0].region == AWS_REGION_EU_WEST_1 + + @mock_aws + def test_project_logging_disabled(self): + codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1) + + project_name = "test-project-logging-disabled" + + project_arn = codebuild_client.create_project( + name=project_name, + source={ + "type": "S3", + "location": "test-bucket", + }, + artifacts={ + "type": "NO_ARTIFACTS", + }, + environment={ + "type": "LINUX_CONTAINER", + "image": "aws/codebuild/standard:4.0", + "computeType": "BUILD_GENERAL1_SMALL", + "environmentVariables": [], + }, + serviceRole="arn:aws:iam::123456789012:role/service-role/codebuild-role", + logsConfig={ + "cloudWatchLogs": { + "status": "DISABLED", + } + }, + tags=[ + {"key": "Name", "value": "test"}, + ], + )["project"]["arn"] + + 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_project_logging_enabled.codebuild_project_logging_enabled.codebuild_client", + new=Codebuild(aws_provider), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_logging_enabled.codebuild_project_logging_enabled import ( + codebuild_project_logging_enabled, + ) + + check = codebuild_project_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"CodeBuild project {project_name} does not have logging enabled." + ) + assert result[0].resource_id == project_name + assert result[0].resource_arn == project_arn + assert result[0].resource_tags == [{"key": "Name", "value": "test"}] + assert result[0].region == AWS_REGION_EU_WEST_1 diff --git a/tests/providers/aws/services/codebuild/codebuild_service_test.py b/tests/providers/aws/services/codebuild/codebuild_service_test.py index e974df6415..f58f7fa636 100644 --- a/tests/providers/aws/services/codebuild/codebuild_service_test.py +++ b/tests/providers/aws/services/codebuild/codebuild_service_test.py @@ -5,6 +5,7 @@ import botocore from prowler.providers.aws.services.codebuild.codebuild_service import ( Build, + CloudWatchLogs, Codebuild, Project, s3Logs, @@ -53,11 +54,16 @@ def mock_make_api_call(self, operation_name, kwarg): } ], "logsConfig": { + "cloudWatchLogs": { + "status": "ENABLED", + "groupName": project_name, + "streamName": project_name, + }, "s3Logs": { "status": "ENABLED", "location": "test-bucket", "encryptionDisabled": False, - } + }, }, "tags": [{"key": "Name", "value": project_name}], } @@ -105,5 +111,15 @@ class Test_Codebuild_Service: assert codebuild.projects[project_arn].s3_logs.enabled assert codebuild.projects[project_arn].s3_logs.bucket_location == "test-bucket" assert codebuild.projects[project_arn].s3_logs.encrypted + assert isinstance( + codebuild.projects[project_arn].cloudwatch_logs, CloudWatchLogs + ) + assert codebuild.projects[project_arn].cloudwatch_logs.enabled + assert ( + codebuild.projects[project_arn].cloudwatch_logs.group_name == project_name + ) + assert ( + codebuild.projects[project_arn].cloudwatch_logs.stream_name == project_name + ) assert codebuild.projects[project_arn].tags[0]["key"] == "Name" assert codebuild.projects[project_arn].tags[0]["value"] == project_name