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(secretsmanager): add new check secretsmanager_secret_rotated_periodically #5450

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
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
1 change: 1 addition & 0 deletions docs/tutorials/configuration_file.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ The following list includes all the AWS checks with configurable variables that
| `rds_instance_backup_enabled` | `check_rds_instance_replicas` | Boolean |
| `securityhub_enabled` | `mute_non_default_regions` | Boolean |
| `secretsmanager_secret_unused` | `max_days_secret_unused` | Integer |
| `secretsmanager_secret_rotated_periodically` | `max_days_secret_unrotated` | Integer |
| `ssm_document_secrets` | `secrets_ignore_patterns` | List of Strings |
| `trustedadvisor_premium_support_plan_subscribed` | `verify_premium_support_plans` | Boolean |
| `vpc_endpoint_connections_trust_boundaries` | `trusted_account_ids` | List of Strings |
Expand Down
4 changes: 4 additions & 0 deletions prowler/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,10 @@ aws:
# Maximum number of days a secret can be unused
max_days_secret_unused: 90

# aws.secretsmanager_secret_rotated_periodically
# Maximum number of days a secret should be rotated
max_days_secret_unrotated: 90

# Azure Configuration
azure:
# Azure Network Configuration
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"Provider": "aws",
"CheckID": "secretsmanager_secret_rotated_periodically",
"CheckTitle": "Secrets should be rotated periodically",
"CheckType": [],
"ServiceName": "secretsmanager",
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:secretsmanager:region:account-id:secret:secret-name",
"Severity": "medium",
"ResourceType": "AwsSecretsManagerSecret",
"Description": "Secrets should be rotated periodically to reduce the risk of unauthorized access.",
"Risk": "Rotating secrets in your AWS account reduces the risk of unauthorized access, especially for credentials like passwords or API keys. Automatic rotation via AWS Secrets Manager replaces long-term secrets with short-term ones, lowering the chances of compromise.",
"RelatedUrl": "https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets.html",
"Remediation": {
"Code": {
"CLI": "aws secretsmanager rotate-secret --secret-id <secret-name>",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/secretsmanager-controls.html#secretsmanager-4",
"Terraform": ""
},
"Recommendation": {
"Text": "Configure automatic rotation for your Secrets Manager secrets.",
"Url": "https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotate-secrets_lambda.html"
}
},
"Categories": [
"secrets"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from datetime import datetime, timezone
from typing import List

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.secretsmanager.secretsmanager_client import (
secretsmanager_client,
)


class secretsmanager_secret_rotated_periodically(Check):
"""Check if AWS Secret Manager secrets are rotated periodically.

This class checks if each secret in AWS Secret Manager has been rotated periodically
the maximum number of days allowed could be configured in the audit_config file as max_days_secret_unrotated.
"""

def execute(self) -> List[Check_Report_AWS]:
"""Execute secretsmanager_secret_rotated_periodically check.

Iterate over all secrets in AWS Secret Manager and check if each secret has been rotated in the past
max_days_secret_unrotated days.

Returns:
List of reports objects for each secret in AWS Secret Manager.
"""
findings = []
for secret in secretsmanager_client.secrets.values():
report = Check_Report_AWS(self.metadata())
report.resource_id = secret.name
report.resource_arn = secret.arn
report.region = secret.region
report.resource_tags = secret.tags
report.status = "PASS"
report.status_extended = f"Secret {secret.name} was last rotated on {secret.last_rotated_date.strftime('%B %d, %Y')} UTC."

if secret.last_rotated_date == datetime.min.replace(tzinfo=timezone.utc):
report.status = "FAIL"
report.status_extended = f"Secret {secret.name} has never been rotated."
else:
days_since_last_rotation = (
datetime.now(timezone.utc) - secret.last_rotated_date
).days

if days_since_last_rotation > secretsmanager_client.audit_config.get(
"max_days_secret_unrotated", 90
):
report.status = "FAIL"
report.status_extended = f"Secret {secret.name} has not been rotated in {days_since_last_rotation} days, which is more than the maximum allowed of {secretsmanager_client.audit_config.get('max_days_secret_unrotated', 90)} days."

findings.append(report)

return findings
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
from datetime import datetime, timezone
from typing import Optional
from typing import Dict, List, Optional

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
from prowler.providers.aws.lib.service.service import AWSService


class SecretsManager(AWSService):
"""AWS Secrets Manager service class to list secrets."""

def __init__(self, provider):
# Call AWSService's __init__
"""Initialize SecretsManager service.

Args:
provider: The AWS provider instance.
"""
super().__init__(__class__.__name__, provider)
self.secrets = {}
self.__threading_call__(self._list_secrets)

def _list_secrets(self, regional_client):
"""List all secrets in the region.

Args:
regional_client: The regional AWS client to list secrets.
"""
logger.info("SecretsManager - Listing Secrets...")
try:
list_secrets_paginator = regional_client.get_paginator("list_secrets")
Expand All @@ -29,15 +40,15 @@ def _list_secrets(self, regional_client):
arn=secret["ARN"],
name=secret["Name"],
region=regional_client.region,
rotation_enabled=secret.get("RotationEnabled", False),
last_rotated_date=secret.get(
"LastRotatedDate", datetime.min
).replace(tzinfo=timezone.utc),
last_accessed_date=secret.get(
"LastAccessedDate", datetime.min
).replace(tzinfo=timezone.utc),
tags=secret.get("Tags"),
)
if "RotationEnabled" in secret:
self.secrets[secret["ARN"]].rotation_enabled = secret[
"RotationEnabled"
]

except Exception as error:
logger.error(
Expand All @@ -52,5 +63,6 @@ class Secret(BaseModel):
name: str
region: str
rotation_enabled: bool = False
last_rotated_date: datetime
last_accessed_date: datetime
tags: Optional[list] = []
tags: Optional[List[Dict[str, str]]] = Field(default_factory=list)
1 change: 1 addition & 0 deletions tests/config/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ def mock_prowler_get_latest_release(_, **kwargs):
"elbv2_min_azs": 2,
"secrets_ignore_patterns": [],
"max_days_secret_unused": 90,
"max_days_secret_unrotated": 90,
}

config_azure = {
Expand Down
4 changes: 4 additions & 0 deletions tests/config/fixtures/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ aws:
# Maximum number of days a secret can be unused
max_days_secret_unused: 90

# aws.secretsmanager_secret_rotated_periodically
# Maximum number of days a secret should be rotated
max_days_secret_unrotated: 90

# Azure Configuration
azure:
# Azure Network Configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def test_secret_rotation_disabled(self):
name=secret_name,
rotation_enabled=False,
last_accessed_date=datetime.min,
last_rotated_date=datetime.min,
)
}
with mock.patch(
Expand Down Expand Up @@ -76,6 +77,7 @@ def test_secret_rotation_enabled(self):
name=secret_name,
rotation_enabled=True,
last_accessed_date=datetime.min,
last_rotated_date=datetime.min,
)
}
with mock.patch(
Expand Down
Loading