Skip to content
Open
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
4 changes: 4 additions & 0 deletions codecov.yml
Copy link
Member

Choose a reason for hiding this comment

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

Please, remove this changes. Don't worry about codecov since it's not a blocker for merging PRs, I'll talk with the team about this to see if there is a problem with the current settings.

Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ component_management:

comment:
layout: "header, diff, flags, components"

flags:
api:
carryforward: true
1 change: 1 addition & 0 deletions prowler/CHANGELOG.md
Copy link
Member

Choose a reason for hiding this comment

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

Please, move it to the current UNRELEASED version and follow the same style as other new checks in the Added section

Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- CIS 6.0 for M365 provider [(#9779)](https://github.com/prowler-cloud/prowler/pull/9779)
- CIS 5.0 compliance framework for the Azure provider [(#9777)](https://github.com/prowler-cloud/prowler/pull/9777)
- `Cloudflare` Bot protection, WAF, Privacy, Anti-Scraping and Zone configuration checks [(#9425)](https://github.com/prowler-cloud/prowler/pull/9425)
- feat(aws): add check rds_instance_extended_support [(#9865)](https://github.com/prowler-cloud/prowler/pull/9865)

### Changed

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"Provider": "aws",
"CheckID": "rds_instance_extended_support",
"CheckTitle": "RDS instances are not enrolled in RDS Extended Support",
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
"CheckTitle": "RDS instances are not enrolled in RDS Extended Support",
"CheckTitle": "RDS instance is not enrolled in RDS Extended Support",

"CheckType": [
"Software and Configuration Checks/Patch Management",
"Software and Configuration Checks/AWS Security Best Practices"
],
"ServiceName": "rds",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AwsRdsDbInstance",
"ResourceGroup": "database",
"Description": "Checks whether Amazon RDS DB instances are enrolled in Amazon RDS Extended Support. If the instance reports `EngineLifecycleSupport` as `open-source-rds-extended-support`, it is enrolled and the check fails. Otherwise, the check passes.",
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
"Description": "Checks whether Amazon RDS DB instances are enrolled in Amazon RDS Extended Support. If the instance reports `EngineLifecycleSupport` as `open-source-rds-extended-support`, it is enrolled and the check fails. Otherwise, the check passes.",
"Description": "**RDS DB instances** are evaluated for enrollment in Amazon RDS Extended Support. The check fails if `EngineLifecycleSupportis` set to `open-source-rds-extended-support`, indicating the instance will incur additional charges after standard support ends.",

"Risk": "DB instances enrolled in RDS Extended Support can incur additional charges after the end of standard support for the running database major version. Remaining on older major versions can also delay necessary upgrades, increasing operational and security risk.",
"RelatedUrl": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/extended-support.html",
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
"RelatedUrl": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/extended-support.html",
"RelatedUrl": "",

"AdditionalURLs": [
"https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/extended-support-viewing.html",
"https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/extended-support-charges.html",
"https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/extended-support-creating-db-instance.html"
],
"Remediation": {
"Code": {
"CLI": "aws rds modify-db-instance --db-instance-identifier <DB_INSTANCE_IDENTIFIER> --engine-version <TARGET_ENGINE_VERSION> --allow-major-version-upgrade --apply-immediately\n# For new DB instances created via automation, prevent enrollment by setting the lifecycle option:\naws rds create-db-instance ... --engine-lifecycle-support open-source-rds-extended-support-disabled",
"NativeIaC": "```yaml\n# CloudFormation: upgrade RDS engine version for an existing instance\nResources:\n <example_resource_name>:\n Type: AWS::RDS::DBInstance\n Properties:\n DBInstanceIdentifier: <example_resource_id>\n Engine: <engine>\n DBInstanceClass: db.t3.micro\n EngineVersion: <SUPPORTED_ENGINE_VERSION> # CRITICAL: move to a supported engine version\n AllowMajorVersionUpgrade: true # CRITICAL: required if upgrading major version\n ApplyImmediately: true # CRITICAL: apply change now to pass the check\n```",
"Other": "If your automation (CloudFormation/Terraform/SDK) creates or restores DB instances, set EngineLifecycleSupport/LifeCycleSupport to open-source-rds-extended-support-disabled where supported, and ensure your upgrade process keeps engines within standard support.",
"Terraform": "```hcl\n# Upgrade RDS engine version\nresource \"aws_db_instance\" \"<example_resource_name>\" {\n identifier = \"<example_resource_id>\"\n engine = \"<engine>\"\n instance_class = \"db.t3.micro\"\n allocated_storage = 20\n\n engine_version = \"<SUPPORTED_ENGINE_VERSION>\" # CRITICAL: use a supported version\n allow_major_version_upgrade = true # CRITICAL: needed for major upgrades\n apply_immediately = true # CRITICAL: apply now to pass the check\n}\n```"
},
"Recommendation": {
"Text": "Upgrade enrolled DB instances to an engine version covered under standard support to stop Extended Support charges. For new DB instances and restores created via automation, explicitly set the engine lifecycle support option to avoid unintended enrollment in RDS Extended Support when that is your policy.",
"Url": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/extended-support-charges.html"
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
"Url": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/extended-support-charges.html"
"Url": "https://hub.prowler.com/check/rds_instance_extended_support"

}
},
"Categories": [
"vulnerabilities"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Prowler check: rds_instance_extended_support

This check fails when an RDS DB instance is enrolled in Amazon RDS Extended Support.
Enrollment is exposed via the "EngineLifecycleSupport" attribute returned by DescribeDBInstances.
"""

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.rds.rds_client import rds_client


class rds_instance_extended_support(Check):
def execute(self):
findings = []

for db_instance in rds_client.db_instances.values():
report = Check_Report_AWS(metadata=self.metadata(), resource=db_instance)

# EngineLifecycleSupport can be absent when Extended Support is not applicable.
lifecycle_support = getattr(db_instance, "engine_lifecycle_support", None)

if lifecycle_support == "open-source-rds-extended-support":
report.status = "FAIL"
report.status_extended = (
f"RDS instance {db_instance.id} ({db_instance.engine} {db_instance.engine_version}) "
f"is enrolled in RDS Extended Support (EngineLifecycleSupport={lifecycle_support})."
)
else:
report.status = "PASS"
report.status_extended = (
f"RDS instance {db_instance.id} ({db_instance.engine} {db_instance.engine_version}) "
"is not enrolled in RDS Extended Support."
)

findings.append(report)

return findings
4 changes: 4 additions & 0 deletions prowler/providers/aws/services/rds/rds_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ def _describe_db_instances(self, regional_client):
endpoint=instance.get("Endpoint", {}),
engine=instance["Engine"],
engine_version=instance["EngineVersion"],
engine_lifecycle_support=instance.get(
"EngineLifecycleSupport"
),
status=instance["DBInstanceStatus"],
public=instance.get("PubliclyAccessible", False),
encrypted=instance["StorageEncrypted"],
Expand Down Expand Up @@ -531,6 +534,7 @@ class DBInstance(BaseModel):
endpoint: dict
engine: str
engine_version: str
engine_lifecycle_support: Optional[str] = None
status: str
public: bool
encrypted: bool
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
from unittest import mock
from unittest.mock import patch

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):
"""
Moto's RDS implementation does not currently expose EngineLifecycleSupport on DescribeDBInstances.
This patch injects it into the response so that Prowler's RDS service can map it onto the DBInstance model.

The check under test fails when:
EngineLifecycleSupport == "open-source-rds-extended-support"
"""
response = make_api_call(self, operation_name, kwarg)

if operation_name == "DescribeDBInstances":
for instance in response.get("DBInstances", []):
if instance.get("DBInstanceIdentifier") == "db-extended-1":
instance["EngineLifecycleSupport"] = "open-source-rds-extended-support"
return response

return response


@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call)
class Test_rds_instance_extended_support:
@mock_aws
def test_rds_no_instances(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_instance_extended_support.rds_instance_extended_support.rds_client",
new=RDS(aws_provider),
):
# Test Check
from prowler.providers.aws.services.rds.rds_instance_extended_support.rds_instance_extended_support import (
rds_instance_extended_support,
)

check = rds_instance_extended_support()
result = check.execute()

assert len(result) == 0

@mock_aws
def test_rds_instance_not_enrolled_in_extended_support(self):
conn = client("rds", region_name=AWS_REGION_US_EAST_1)
conn.create_db_instance(
DBInstanceIdentifier="db-standard-1",
AllocatedStorage=10,
Engine="postgres",
EngineVersion="8.0.32",
DBName="staging-postgres",
DBInstanceClass="db.m1.small",
PubliclyAccessible=False,
)

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

check = rds_instance_extended_support()
result = check.execute()

assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "RDS instance db-standard-1 (postgres 8.0.32) is not enrolled in RDS Extended Support."
)
assert result[0].resource_id == "db-standard-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}:db:db-standard-1"
)
assert result[0].resource_tags == []

@mock_aws
def test_rds_instance_enrolled_in_extended_support(self):
conn = client("rds", region_name=AWS_REGION_US_EAST_1)
conn.create_db_instance(
DBInstanceIdentifier="db-extended-1",
AllocatedStorage=10,
Engine="postgres",
EngineVersion="8.0.32",
DBName="staging-postgres",
DBInstanceClass="db.m1.small",
PubliclyAccessible=False,
)

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

check = rds_instance_extended_support()
result = check.execute()

assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "RDS instance db-extended-1 (postgres 8.0.32) is enrolled in RDS Extended Support "
"(EngineLifecycleSupport=open-source-rds-extended-support)."
)
assert result[0].resource_id == "db-extended-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}:db:db-extended-1"
)
assert result[0].resource_tags == []
Loading