feat(secretsmanager): add new check secretsmanager_secret_unused (#5428)

This commit is contained in:
Rubén De la Torre Vico
2024-10-17 16:24:12 +02:00
committed by GitHub
parent 5d51942768
commit a17cf1bbb6
11 changed files with 304 additions and 1 deletions
+1
View File
@@ -56,6 +56,7 @@ The following list includes all the AWS checks with configurable variables that
| `organizations_scp_check_deny_regions` | `organizations_enabled_regions` | List of Strings |
| `rds_instance_backup_enabled` | `check_rds_instance_replicas` | Boolean |
| `securityhub_enabled` | `mute_non_default_regions` | Boolean |
| `secretsmanager_secret_unused` | `max_days_secret_unused` | 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 |
+5
View File
@@ -359,6 +359,11 @@ aws:
# Patterns to ignore in the secrets checks
secrets_ignore_patterns: []
# AWS Secrets Manager Configuration
# aws.secretsmanager_secret_unused
# Maximum number of days a secret can be unused
max_days_secret_unused: 90
# Azure Configuration
azure:
# Azure Network Configuration
@@ -0,0 +1,32 @@
{
"Provider": "aws",
"CheckID": "secretsmanager_secret_unused",
"CheckTitle": "Ensure secrets manager secrets are not unused",
"CheckType": [],
"ServiceName": "secretsmanager",
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:secretsmanager:region:account-id:secret:secret-name",
"Severity": "medium",
"ResourceType": "AwsSecretsManagerSecret",
"Description": "Checks whether Secrets Manager secrets are unused.",
"Risk": "Unused secrets can be abused by former users or leaked to unauthorized entities, increasing the risk of unauthorized access and data breaches.",
"RelatedUrl": "https://docs.aws.amazon.com/secretsmanager/latest/userguide/manage_delete-secret.html",
"Remediation": {
"Code": {
"CLI": "aws secretsmanager delete-secret --secret-id <secret-arn>",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/secretsmanager-controls.html#secretsmanager-3",
"Terraform": ""
},
"Recommendation": {
"Text": "Regularly review Secrets Manager secrets and delete those that are no longer in use.",
"Url": "https://docs.aws.amazon.com/secretsmanager/latest/userguide/manage_delete-secret.html"
}
},
"Categories": [
"secrets"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,38 @@
from datetime import datetime, timedelta, timezone
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.secretsmanager.secretsmanager_client import (
secretsmanager_client,
)
class secretsmanager_secret_unused(Check):
def execute(self):
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} has been accessed recently, last accessed on {secret.last_accessed_date.strftime('%B %d, %Y')}."
if (datetime.now(timezone.utc) - secret.last_accessed_date) > timedelta(
days=secretsmanager_client.audit_config.get(
"max_days_secret_unused", 90
)
):
report.status = "FAIL"
if secret.last_accessed_date == datetime.min.replace(
tzinfo=timezone.utc
):
report.status_extended = (
f"Secret {secret.name} has never been accessed."
)
else:
report.status_extended = f"Secret {secret.name} has not been accessed since {secret.last_accessed_date.strftime('%B %d, %Y')}, you should review if it is still needed."
findings.append(report)
return findings
@@ -1,3 +1,4 @@
from datetime import datetime, timezone
from typing import Optional
from pydantic import BaseModel
@@ -7,7 +8,6 @@ from prowler.lib.scan_filters.scan_filters import is_resource_filtered
from prowler.providers.aws.lib.service.service import AWSService
################## SecretsManager
class SecretsManager(AWSService):
def __init__(self, provider):
# Call AWSService's __init__
@@ -29,6 +29,9 @@ class SecretsManager(AWSService):
arn=secret["ARN"],
name=secret["Name"],
region=regional_client.region,
last_accessed_date=secret.get(
"LastAccessedDate", datetime.min
).replace(tzinfo=timezone.utc),
tags=secret.get("Tags"),
)
if "RotationEnabled" in secret:
@@ -49,4 +52,5 @@ class Secret(BaseModel):
name: str
region: str
rotation_enabled: bool = False
last_accessed_date: datetime
tags: Optional[list] = []
+1
View File
@@ -310,6 +310,7 @@ config_aws = {
"elb_min_azs": 2,
"elbv2_min_azs": 2,
"secrets_ignore_patterns": [],
"max_days_secret_unused": 90,
}
config_azure = {
+5
View File
@@ -355,6 +355,11 @@ aws:
# Patterns to ignore in the secrets checks
secrets_ignore_patterns: []
# AWS Secrets Manager Configuration
# aws.secretsmanager_secret_unused
# Maximum number of days a secret can be unused
max_days_secret_unused: 90
# Azure Configuration
azure:
# Azure Network Configuration
@@ -1,3 +1,4 @@
from datetime import datetime
from unittest import mock
from prowler.providers.aws.services.secretsmanager.secretsmanager_service import Secret
@@ -8,9 +9,13 @@ class Test_secretsmanager_automatic_rotation_enabled:
def test_no_secrets(self):
secretsmanager_client = mock.MagicMock
secretsmanager_client.secrets = {}
with mock.patch(
"prowler.providers.aws.services.secretsmanager.secretsmanager_service.SecretsManager",
new=secretsmanager_client,
), mock.patch(
"prowler.providers.aws.services.secretsmanager.secretsmanager_client.secretsmanager_client",
new=secretsmanager_client,
):
# Test Check
from prowler.providers.aws.services.secretsmanager.secretsmanager_automatic_rotation_enabled.secretsmanager_automatic_rotation_enabled import (
@@ -32,11 +37,15 @@ class Test_secretsmanager_automatic_rotation_enabled:
region=AWS_REGION_EU_WEST_1,
name=secret_name,
rotation_enabled=False,
last_accessed_date=datetime.min,
)
}
with mock.patch(
"prowler.providers.aws.services.secretsmanager.secretsmanager_service.SecretsManager",
new=secretsmanager_client,
), mock.patch(
"prowler.providers.aws.services.secretsmanager.secretsmanager_client.secretsmanager_client",
new=secretsmanager_client,
):
# Test Check
from prowler.providers.aws.services.secretsmanager.secretsmanager_automatic_rotation_enabled.secretsmanager_automatic_rotation_enabled import (
@@ -66,11 +75,15 @@ class Test_secretsmanager_automatic_rotation_enabled:
region=AWS_REGION_EU_WEST_1,
name=secret_name,
rotation_enabled=True,
last_accessed_date=datetime.min,
)
}
with mock.patch(
"prowler.providers.aws.services.secretsmanager.secretsmanager_service.SecretsManager",
new=secretsmanager_client,
), mock.patch(
"prowler.providers.aws.services.secretsmanager.secretsmanager_client.secretsmanager_client",
new=secretsmanager_client,
):
# Test Check
from prowler.providers.aws.services.secretsmanager.secretsmanager_automatic_rotation_enabled.secretsmanager_automatic_rotation_enabled import (
@@ -0,0 +1,200 @@
from datetime import datetime, timezone
from unittest.mock import patch
import botocore
from boto3 import client
from freezegun import freeze_time
from moto import mock_aws
from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider
orig = botocore.client.BaseClient._make_api_call
def mock_make_api_call_secret_accessed_100_days_ago(self, operation_name, kwarg):
if operation_name == "ListSecrets":
return {
"SecretList": [
{
"ARN": "arn:aws:secretsmanager:eu-west-1:123456789012:secret:test-100-days-secret",
"Name": "test-100-days-secret",
"LastAccessedDate": datetime(
2023, 1, 1, 0, 0, 0, tzinfo=timezone.utc
),
"Tags": [{"Key": "Name", "Value": "test-100-days-secret"}],
}
]
}
# If we don't want to patch the API call
return orig(self, operation_name, kwarg)
def mock_make_api_call_secret_accessed_yesterday(self, operation_name, kwarg):
if operation_name == "ListSecrets":
return {
"SecretList": [
{
"ARN": "arn:aws:secretsmanager:eu-west-1:123456789012:secret:test-secret",
"Name": "test-secret",
"LastAccessedDate": datetime(
2023, 4, 9, 0, 0, 0, tzinfo=timezone.utc
),
"Tags": [{"Key": "Name", "Value": "test-secret"}],
}
]
}
# If we don't want to patch the API call
return orig(self, operation_name, kwarg)
class Test_secretsmanager_secret_unused:
def test_no_secrets(self):
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
from prowler.providers.aws.services.secretsmanager.secretsmanager_service import (
SecretsManager,
)
with patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), patch(
"prowler.providers.aws.services.secretsmanager.secretsmanager_secret_unused.secretsmanager_secret_unused.secretsmanager_client",
new=SecretsManager(aws_provider),
):
# Test Check
from prowler.providers.aws.services.secretsmanager.secretsmanager_secret_unused.secretsmanager_secret_unused import (
secretsmanager_secret_unused,
)
check = secretsmanager_secret_unused()
result = check.execute()
assert len(result) == 0
@mock_aws
def test_secret_never_used(self):
secretsmanager_client = client(
"secretsmanager", region_name=AWS_REGION_EU_WEST_1
)
secret_arn = secretsmanager_client.create_secret(
Name="test-secret",
Tags=[
{"Key": "Name", "Value": "test-secret"},
],
)["ARN"]
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
from prowler.providers.aws.services.secretsmanager.secretsmanager_service import (
SecretsManager,
)
with patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), patch(
"prowler.providers.aws.services.secretsmanager.secretsmanager_secret_unused.secretsmanager_secret_unused.secretsmanager_client",
new=SecretsManager(aws_provider),
):
from prowler.providers.aws.services.secretsmanager.secretsmanager_secret_unused.secretsmanager_secret_unused import (
secretsmanager_secret_unused,
)
check = secretsmanager_secret_unused()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Secret test-secret has never been accessed."
)
assert result[0].resource_id == "test-secret"
assert result[0].resource_arn == secret_arn
assert result[0].region == AWS_REGION_EU_WEST_1
assert result[0].resource_tags == [{"Key": "Name", "Value": "test-secret"}]
@freeze_time("2023-04-10")
@patch(
"botocore.client.BaseClient._make_api_call",
new=mock_make_api_call_secret_accessed_100_days_ago,
)
@mock_aws
def test_secret_unused_for_last_100_days(self):
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
from prowler.providers.aws.services.secretsmanager.secretsmanager_service import (
SecretsManager,
)
with patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), patch(
"prowler.providers.aws.services.secretsmanager.secretsmanager_secret_unused.secretsmanager_secret_unused.secretsmanager_client",
new=SecretsManager(aws_provider),
):
from prowler.providers.aws.services.secretsmanager.secretsmanager_secret_unused.secretsmanager_secret_unused import (
secretsmanager_secret_unused,
)
check = secretsmanager_secret_unused()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Secret test-100-days-secret has not been accessed since January 01, 2023, you should review if it is still needed."
)
assert result[0].resource_id == "test-100-days-secret"
assert (
result[0].resource_arn
== "arn:aws:secretsmanager:eu-west-1:123456789012:secret:test-100-days-secret"
)
assert result[0].region == AWS_REGION_EU_WEST_1
assert result[0].resource_tags == [
{"Key": "Name", "Value": "test-100-days-secret"}
]
@freeze_time("2023-04-10")
@patch(
"botocore.client.BaseClient._make_api_call",
new=mock_make_api_call_secret_accessed_yesterday,
)
@mock_aws
def test_secret_used_yesterday(self):
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
from prowler.providers.aws.services.secretsmanager.secretsmanager_service import (
SecretsManager,
)
with patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), patch(
"prowler.providers.aws.services.secretsmanager.secretsmanager_secret_unused.secretsmanager_secret_unused.secretsmanager_client",
new=SecretsManager(aws_provider),
):
from prowler.providers.aws.services.secretsmanager.secretsmanager_secret_unused.secretsmanager_secret_unused import (
secretsmanager_secret_unused,
)
check = secretsmanager_secret_unused()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Secret test-secret has been accessed recently, last accessed on April 09, 2023."
)
assert result[0].resource_id == "test-secret"
assert result[0].resource_arn == (
"arn:aws:secretsmanager:eu-west-1:123456789012:secret:test-secret"
)
assert result[0].region == AWS_REGION_EU_WEST_1
assert result[0].resource_tags == [{"Key": "Name", "Value": "test-secret"}]
@@ -1,5 +1,6 @@
import io
import zipfile
from datetime import datetime, timezone
from unittest.mock import patch
from boto3 import client, resource
@@ -133,6 +134,9 @@ class Test_SecretsManager_Service:
assert secretsmanager.secrets[secret_arn].arn == secret_arn
assert secretsmanager.secrets[secret_arn].region == AWS_REGION_EU_WEST_1
assert secretsmanager.secrets[secret_arn].rotation_enabled is True
assert secretsmanager.secrets[
secret_arn
].last_accessed_date == datetime.min.replace(tzinfo=timezone.utc)
assert secretsmanager.secrets[secret_arn].tags == [
{"Key": "test", "Value": "test"},
]