feat(glue): add new check glue_etl_jobs_logging_enabled (#5581)

Co-authored-by: Sergio <sergio@prowler.com>
This commit is contained in:
Hugo Pereira Brito
2024-10-30 16:56:46 +01:00
committed by GitHub
parent 239b248935
commit 056d482023
5 changed files with 223 additions and 8 deletions
@@ -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-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": ""
}
@@ -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
@@ -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)
@@ -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"}]