feat(aws): add check rds_instance_extended_support (#9865)

Co-authored-by: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com>
This commit is contained in:
Kay Agahd
2026-01-28 16:49:35 +01:00
committed by GitHub
parent 143d4b7c29
commit 04e2d15dd2
6 changed files with 238 additions and 0 deletions
+1
View File
@@ -12,6 +12,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `exchange_shared_mailbox_sign_in_disabled` check for M365 provider [(#9828)](https://github.com/prowler-cloud/prowler/pull/9828)
- CloudTrail Timeline abstraction for querying resource modification history [(#9101)](https://github.com/prowler-cloud/prowler/pull/9101)
- Cloudflare `--account-id` filter argument [(#9894)](https://github.com/prowler-cloud/prowler/pull/9894)
- `rds_instance_extended_support` check for AWS provider [(#9865)](https://github.com/prowler-cloud/prowler/pull/9865)
### Changed
@@ -0,0 +1,41 @@
{
"Provider": "aws",
"CheckID": "rds_instance_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": "**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": "",
"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://hub.prowler.com/check/rds_instance_extended_support"
}
},
"Categories": [
"vulnerabilities"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -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
@@ -59,6 +59,9 @@ class RDS(AWSService):
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"],
@@ -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
@@ -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 == []