diff --git a/prowler/providers/aws/services/glue/glue_etl_jobs_logging_enabled/__init__.py b/prowler/providers/aws/services/glue/glue_etl_jobs_logging_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/glue/glue_etl_jobs_logging_enabled/glue_etl_jobs_logging_enabled.metadata.json b/prowler/providers/aws/services/glue/glue_etl_jobs_logging_enabled/glue_etl_jobs_logging_enabled.metadata.json new file mode 100644 index 0000000000..59175732d3 --- /dev/null +++ b/prowler/providers/aws/services/glue/glue_etl_jobs_logging_enabled/glue_etl_jobs_logging_enabled.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "glue_etl_jobs_logging_enabled", + "CheckTitle": "Check if Glue ETL Jobs have logging enabled.", + "CheckType": [ + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices" + ], + "ServiceName": "glue", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:glue:region:account-id:job/job-name", + "Severity": "medium", + "ResourceType": "AwsGlueJob", + "Description": "Ensure that Glue ETL Jobs have CloudWatch logs enabled.", + "Risk": "Without logging enabled, AWS Glue jobs lack visibility into job activities and failures, making it difficult to detect unauthorized access, troubleshoot issues, and ensure compliance. This may result in untracked security incidents or operational issues that affect data processing.", + "RelatedUrl": "https://docs.aws.amazon.com/glue/latest/dg/monitor-continuous-logging.html", + "Remediation": { + "Code": { + "CLI": "aws glue update-job --job-name --job-update \"Command={DefaultArguments={--enable-continuous-cloudwatch-log=true}}\"", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/glue-controls.html#glue-2", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable logging for AWS Glue jobs to capture and monitor job events. Logging allows for better visibility into job performance, error detection, and security oversight.", + "Url": "https://docs.aws.amazon.com/glue/latest/dg/monitor-continuous-logging-enable.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/glue/glue_etl_jobs_logging_enabled/glue_etl_jobs_logging_enabled.py b/prowler/providers/aws/services/glue/glue_etl_jobs_logging_enabled/glue_etl_jobs_logging_enabled.py new file mode 100644 index 0000000000..d2a6596897 --- /dev/null +++ b/prowler/providers/aws/services/glue/glue_etl_jobs_logging_enabled/glue_etl_jobs_logging_enabled.py @@ -0,0 +1,42 @@ +from typing import List + +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.glue.glue_client import glue_client + + +class glue_etl_jobs_logging_enabled(Check): + """Check if Glue ETL jobs have logging enabled. + + This check will return FAIL if the Glue ETL job does not have logging enabled. + """ + + def execute(self) -> List[Check_Report_AWS]: + """Execute the Glue ETL jobs logging enabled check. + + Iterate over all Glue ETL jobs and check if they have logging enabled. + + Returns: + List[Check_Report_AWS]: A list of report objects with the results of the check. + """ + findings = [] + for job in glue_client.jobs: + report = Check_Report_AWS(self.metadata()) + report.resource_id = job.name + report.resource_arn = job.arn + report.region = job.region + report.resource_tags = job.tags + report.status = "FAIL" + report.status_extended = ( + f"Glue job {job.name} does not have logging enabled." + ) + + if ( + job.arguments.get("--enable-continuous-cloudwatch-log", "false") + == "true" + ): + report.status = "PASS" + report.status_extended = f"Glue job {job.name} have logging enabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/glue/glue_service.py b/prowler/providers/aws/services/glue/glue_service.py index a0e292b54e..21e7fd4907 100644 --- a/prowler/providers/aws/services/glue/glue_service.py +++ b/prowler/providers/aws/services/glue/glue_service.py @@ -1,8 +1,8 @@ import json -from typing import Optional +from typing import Dict, List, Optional from botocore.exceptions import ClientError -from pydantic import BaseModel +from pydantic import BaseModel, Field from prowler.lib.logger import logger from prowler.lib.scan_filters.scan_filters import is_resource_filtered @@ -112,7 +112,7 @@ class Glue(AWSService): name=job["Name"], arn=arn, security=job.get("SecurityConfiguration"), - arguments=job.get("DefaultArguments"), + arguments=job.get("DefaultArguments", {}), region=regional_client.region, ) ) @@ -277,7 +277,7 @@ class Connection(BaseModel): type: str properties: dict region: str - tags: Optional[list] + tags: Optional[List[Dict[str, str]]] = Field(default_factory=list) class Table(BaseModel): @@ -286,6 +286,7 @@ class Table(BaseModel): database: str catalog: Optional[str] region: str + tags: Optional[List[Dict[str, str]]] = Field(default_factory=list) class CatalogEncryptionSetting(BaseModel): @@ -300,16 +301,16 @@ class DevEndpoint(BaseModel): arn: str security: Optional[str] region: str - tags: Optional[list] + tags: Optional[List[Dict[str, str]]] = Field(default_factory=list) class Job(BaseModel): arn: str name: str security: Optional[str] - arguments: Optional[dict] + arguments: Optional[Dict[str, str]] = Field(default_factory=dict) region: str - tags: Optional[list] + tags: Optional[List[Dict[str, str]]] = Field(default_factory=list) class SecurityConfig(BaseModel): @@ -321,6 +322,7 @@ class SecurityConfig(BaseModel): jb_encryption: str jb_key_arn: Optional[str] region: str + tags: Optional[List[Dict[str, str]]] = Field(default_factory=list) class MLTransform(BaseModel): @@ -329,7 +331,7 @@ class MLTransform(BaseModel): name: str user_data_encryption: str region: str - tags: Optional[list] + tags: Optional[List[Dict[str, str]]] = Field(default_factory=list) class DataCatalog(BaseModel): @@ -337,3 +339,4 @@ class DataCatalog(BaseModel): region: str encryption_settings: Optional[CatalogEncryptionSetting] policy: Optional[dict] + tags: Optional[List[Dict[str, str]]] = Field(default_factory=list) diff --git a/tests/providers/aws/services/glue/glue_etl_jobs_logging_enabled/glue_etl_jobs_logging_enabled_test.py b/tests/providers/aws/services/glue/glue_etl_jobs_logging_enabled/glue_etl_jobs_logging_enabled_test.py new file mode 100644 index 0000000000..53adcb59ce --- /dev/null +++ b/tests/providers/aws/services/glue/glue_etl_jobs_logging_enabled/glue_etl_jobs_logging_enabled_test.py @@ -0,0 +1,138 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + + +class Test_glue_etl_jobs_logging_enabled: + @mock_aws + def test_glue_no_jobs(self): + from prowler.providers.aws.services.glue.glue_service import Glue + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.glue.glue_etl_jobs_logging_enabled.glue_etl_jobs_logging_enabled.glue_client", + new=Glue(aws_provider), + ): + from prowler.providers.aws.services.glue.glue_etl_jobs_logging_enabled.glue_etl_jobs_logging_enabled import ( + glue_etl_jobs_logging_enabled, + ) + + check = glue_etl_jobs_logging_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_glue_job_logging_enabled(self): + glue_client = client("glue", region_name=AWS_REGION_US_EAST_1) + job_name = "test-job" + job_arn = ( + f"arn:aws:glue:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:job/{job_name}" + ) + glue_client.create_job( + Name=job_name, + Role="role_test", + Command={"Name": "name_test", "ScriptLocation": "script_test"}, + DefaultArguments={"--enable-continuous-cloudwatch-log": "true"}, + Tags={"key_test": "value_test"}, + GlueVersion="1.0", + MaxCapacity=0.0625, + MaxRetries=0, + Timeout=10, + NumberOfWorkers=2, + WorkerType="G.1X", + SecurityConfiguration="sec_config", + NotificationProperty={"NotifyDelayAfter": 1}, + ) + + from prowler.providers.aws.services.glue.glue_service import Glue + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.glue.glue_etl_jobs_logging_enabled.glue_etl_jobs_logging_enabled.glue_client", + new=Glue(aws_provider), + ): + from prowler.providers.aws.services.glue.glue_etl_jobs_logging_enabled.glue_etl_jobs_logging_enabled import ( + glue_etl_jobs_logging_enabled, + ) + + check = glue_etl_jobs_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Glue job {job_name} have logging enabled." + ) + assert result[0].resource_id == job_name + assert result[0].resource_arn == job_arn + assert result[0].resource_tags == [{"key_test": "value_test"}] + + @mock_aws + def test_glue_job_logging_disabled(self): + glue_client = client("glue", region_name=AWS_REGION_US_EAST_1) + job_name = "test-job" + job_arn = ( + f"arn:aws:glue:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:job/{job_name}" + ) + glue_client.create_job( + Name=job_name, + Role="role_test", + Command={"Name": "name_test", "ScriptLocation": "script_test"}, + DefaultArguments={}, + Tags={"key_test": "value_test"}, + GlueVersion="1.0", + MaxCapacity=0.0625, + MaxRetries=0, + Timeout=10, + NumberOfWorkers=2, + WorkerType="G.1X", + SecurityConfiguration="sec_config", + ) + + from prowler.providers.aws.services.glue.glue_service import Glue + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.glue.glue_etl_jobs_logging_enabled.glue_etl_jobs_logging_enabled.glue_client", + new=Glue(aws_provider), + ): + from prowler.providers.aws.services.glue.glue_etl_jobs_logging_enabled.glue_etl_jobs_logging_enabled import ( + glue_etl_jobs_logging_enabled, + ) + + check = glue_etl_jobs_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Glue job {job_name} does not have logging enabled." + ) + assert result[0].resource_id == job_name + assert result[0].resource_arn == job_arn + assert result[0].resource_tags == [{"key_test": "value_test"}]