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 4 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 Aurora MySQL 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 Aurora MySQL cluster is integrated with CloudWatch Logs.",
"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,24 @@
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 = []
for db_cluster_arn, db_cluster in rds_client.db_clusters.items():
if db_cluster.engine == "aurora-mysql":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_CreateDBCluster.html, it is valid for:

Valid for Cluster Type: Aurora DB clusters and Multi-AZ DB clusters

The following values are valid for each DB engine:

Aurora MySQL - audit | error | general | slowquery

Aurora PostgreSQL - postgresql

RDS for MySQL - error | general | slowquery

RDS for PostgreSQL - postgresql | upgrade

Please, make the check look for these engines too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

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"Aurora MySQL Cluster {db_cluster.id} is shipping {', '.join(db_cluster.cloudwatch_logs)} logs to CloudWatch Logs."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
report.status_extended = f"Aurora MySQL Cluster {db_cluster.id} is shipping {', '.join(db_cluster.cloudwatch_logs)} logs to CloudWatch Logs."
report.status_extended = f"RDS Cluster {db_cluster.id} is shipping {', '.join(db_cluster.cloudwatch_logs)} logs to CloudWatch Logs."

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

else:
report.status = "FAIL"
report.status_extended = f"Aurora MySQL Cluster {db_cluster.id} does not have CloudWatch Logs enabled."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
report.status_extended = f"Aurora MySQL Cluster {db_cluster.id} does not have CloudWatch Logs enabled."
report.status_extended = f"RDS Cluster {db_cluster.id} does not have CloudWatch Logs enabled."

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


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,157 @@
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_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_aurora_cluster(self):
conn = client("rds", region_name=AWS_REGION_US_EAST_1)
conn.create_db_cluster(
DBClusterIdentifier="cluster-1",
Engine="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) == 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
== "Aurora MySQL 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
== "Aurora MySQL 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