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(aws): Add new check to ensure Aurora MySQL DB Clusters publish audit logs to CloudWatch logs #4916

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "rds_cluster_integration_cloudwatch_logs",
"CheckTitle": "Check if RDS cluster is integrated with CloudWatch Logs.",
"CheckType": [],
"ServiceName": "rds",
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:rds:region:account-id:db-cluster",
"Severity": "medium",
"ResourceType": "AwsRdsDbCluster",
"Description": "Check if RDS cluster is integrated with CloudWatch Logs. The types valid are Aurora MySQL, Aurora PostgreSQL, MySQL, PostgreSQL.",
"Risk": "If logs are not enabled, monitoring of service use and threat analysis is not possible.",
"RelatedUrl": "https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/USER_LogAccess.html",
"Remediation": {
"Code": {
"CLI": "aws rds modify-db-cluster --db-cluster-identifier <db_cluster_id> --cloudwatch-logs-export-configuration {'EnableLogTypes':['audit',error','general','slowquery']} --apply-immediately",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/rds-controls.html#rds-34",
"Terraform": ""
},
"Recommendation": {
"Text": "Use CloudWatch Logs to perform real-time analysis of the log data. Create alarms and view metrics.",
"Url": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/publishing_cloudwatchlogs.html"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.rds.rds_client import rds_client


class rds_cluster_integration_cloudwatch_logs(Check):
def execute(self):
findings = []
valid_engines = ["aurora-mysql", "aurora-postgresql", "mysql", "postgres"]
for db_cluster_arn, db_cluster in rds_client.db_clusters.items():
if db_cluster.engine in valid_engines:
report = Check_Report_AWS(self.metadata())
report.region = db_cluster.region
report.resource_id = db_cluster.id
report.resource_arn = db_cluster_arn
report.resource_tags = db_cluster.tags
if db_cluster.cloudwatch_logs:
report.status = "PASS"
report.status_extended = f"RDS Cluster {db_cluster.id} is shipping {', '.join(db_cluster.cloudwatch_logs)} logs to CloudWatch Logs."
else:
report.status = "FAIL"
report.status_extended = f"RDS Cluster {db_cluster.id} does not have CloudWatch Logs enabled."

findings.append(report)

return findings
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def execute(self):
report.resource_tags = db_instance.tags
if db_instance.cloudwatch_logs:
report.status = "PASS"
report.status_extended = f"RDS Instance {db_instance.id} is shipping {' '.join(db_instance.cloudwatch_logs)} to CloudWatch Logs."
report.status_extended = f"RDS Instance {db_instance.id} is shipping {', '.join(db_instance.cloudwatch_logs)} logs to CloudWatch Logs."
else:
report.status = "FAIL"
report.status_extended = f"RDS Instance {db_instance.id} does not have CloudWatch Logs enabled."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
from unittest import mock

import botocore
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,
)

make_api_call = botocore.client.BaseClient._make_api_call


def mock_make_api_call(self, operation_name, kwarg):
if operation_name == "CreateDBCluster":
return {
"DBClusterIdentifier": "cluster-1",
"Engine": "aurora",
"MasterUsername": "admin",
"MasterUserPassword": "password",
}

return make_api_call(self, operation_name, kwarg)


class Test_rds_cluster_integration_cloudwatch_logs:
@mock_aws
def test_rds_no_clusters(self):
from prowler.providers.aws.services.rds.rds_service import RDS

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.rds.rds_cluster_integration_cloudwatch_logs.rds_cluster_integration_cloudwatch_logs.rds_client",
new=RDS(aws_provider),
):
# Test Check
from prowler.providers.aws.services.rds.rds_cluster_integration_cloudwatch_logs.rds_cluster_integration_cloudwatch_logs import (
rds_cluster_integration_cloudwatch_logs,
)

check = rds_cluster_integration_cloudwatch_logs()
result = check.execute()

assert len(result) == 0

@mock_aws
def test_rds_no_valid_cluster(self):
with mock.patch(
"botocore.client.BaseClient._make_api_call", new=mock_make_api_call
):
conn = client("rds", region_name=AWS_REGION_US_EAST_1)
conn.create_db_cluster(
DBClusterIdentifier="cluster-1",
Engine="aurora",
MasterUsername="admin",
MasterUserPassword="password",
)

from prowler.providers.aws.services.rds.rds_service import RDS

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.rds.rds_cluster_integration_cloudwatch_logs.rds_cluster_integration_cloudwatch_logs.rds_client",
new=RDS(aws_provider),
):
# Test Check
from prowler.providers.aws.services.rds.rds_cluster_integration_cloudwatch_logs.rds_cluster_integration_cloudwatch_logs import (
rds_cluster_integration_cloudwatch_logs,
)

check = rds_cluster_integration_cloudwatch_logs()
result = check.execute()

assert len(result) == 0

@mock_aws
def test_rds_cluster_no_logs(self):
conn = client("rds", region_name=AWS_REGION_US_EAST_1)
conn.create_db_cluster(
DBClusterIdentifier="aurora-cluster-1",
Engine="aurora-mysql",
MasterUsername="admin",
MasterUserPassword="password",
)

from prowler.providers.aws.services.rds.rds_service import RDS

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.rds.rds_cluster_integration_cloudwatch_logs.rds_cluster_integration_cloudwatch_logs.rds_client",
new=RDS(aws_provider),
):
# Test Check
from prowler.providers.aws.services.rds.rds_cluster_integration_cloudwatch_logs.rds_cluster_integration_cloudwatch_logs import (
rds_cluster_integration_cloudwatch_logs,
)

check = rds_cluster_integration_cloudwatch_logs()
result = check.execute()

assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "RDS Cluster aurora-cluster-1 does not have CloudWatch Logs enabled."
)
assert result[0].resource_id == "aurora-cluster-1"
assert result[0].region == AWS_REGION_US_EAST_1
assert (
result[0].resource_arn
== f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:aurora-cluster-1"
)
assert result[0].resource_tags == []

@mock_aws
def test_rds_cluster_with_logs(self):
conn = client("rds", region_name=AWS_REGION_US_EAST_1)
conn.create_db_cluster(
DBClusterIdentifier="aurora-cluster-1",
Engine="aurora-mysql",
MasterUsername="admin",
MasterUserPassword="password",
EnableCloudwatchLogsExports=["audit", "error"],
)

from prowler.providers.aws.services.rds.rds_service import RDS

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.rds.rds_cluster_integration_cloudwatch_logs.rds_cluster_integration_cloudwatch_logs.rds_client",
new=RDS(aws_provider),
):
# Test Check
from prowler.providers.aws.services.rds.rds_cluster_integration_cloudwatch_logs.rds_cluster_integration_cloudwatch_logs import (
rds_cluster_integration_cloudwatch_logs,
)

check = rds_cluster_integration_cloudwatch_logs()
result = check.execute()

assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "RDS Cluster aurora-cluster-1 is shipping audit, error logs to CloudWatch Logs."
)
assert result[0].resource_id == "aurora-cluster-1"
assert result[0].region == AWS_REGION_US_EAST_1
assert (
result[0].resource_arn
== f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:aurora-cluster-1"
)
assert result[0].resource_tags == []
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def test_rds_instance_with_logs(self):
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "RDS Instance db-master-1 is shipping audit error to CloudWatch Logs."
== "RDS Instance db-master-1 is shipping audit, error logs to CloudWatch Logs."
)
assert result[0].resource_id == "db-master-1"
assert result[0].region == AWS_REGION_US_EAST_1
Expand Down
Loading