mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-04 02:04:06 +00:00
feat(guardduty): assess unified Runtime Monitoring and AI Protection (#12564)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
`guardduty_ai_protection_enabled` check for AWS provider, flagging GuardDuty detectors without AI Protection, which analyzes AWS CloudTrail data events from Amazon Bedrock, Amazon Bedrock AgentCore and Amazon SageMaker AI; a detector that does not report the feature is `MANUAL` rather than `FAIL`
|
||||
@@ -0,0 +1 @@
|
||||
`guardduty_runtime_monitoring_enabled` check for AWS provider, flagging GuardDuty detectors without unified Runtime Monitoring, the only feature that covers Amazon EC2 instances and Amazon ECS on AWS Fargate tasks in addition to Amazon EKS
|
||||
@@ -0,0 +1 @@
|
||||
`guardduty_eks_runtime_monitoring_enabled` no longer reports `FAIL` for detectors that use unified Runtime Monitoring; the GuardDuty service now reads the `RUNTIME_MONITORING` feature, which is mutually exclusive with `EKS_RUNTIME_MONITORING` and already covers Amazon EKS
|
||||
@@ -878,9 +878,11 @@
|
||||
"guardduty_s3_protection_enabled",
|
||||
"guardduty_eks_audit_log_enabled",
|
||||
"guardduty_eks_runtime_monitoring_enabled",
|
||||
"guardduty_runtime_monitoring_enabled",
|
||||
"guardduty_lambda_protection_enabled",
|
||||
"guardduty_rds_protection_enabled",
|
||||
"guardduty_ec2_malware_protection_enabled"
|
||||
"guardduty_ec2_malware_protection_enabled",
|
||||
"guardduty_ai_protection_enabled"
|
||||
],
|
||||
"ConfigRequirements": [
|
||||
{
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "guardduty_ai_protection_enabled",
|
||||
"CheckTitle": "GuardDuty detector has AI Protection enabled",
|
||||
"CheckType": [
|
||||
"Software and Configuration Checks/AWS Security Best Practices/Runtime Behavior Analysis",
|
||||
"TTPs/Credential Access",
|
||||
"Effects/Resource Consumption"
|
||||
],
|
||||
"ServiceName": "guardduty",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AwsGuardDutyDetector",
|
||||
"ResourceGroup": "security",
|
||||
"Description": "Active **Amazon GuardDuty detectors** are assessed for **AI Protection** being enabled, which analyzes AWS CloudTrail data events from Amazon Bedrock, Amazon Bedrock AgentCore and Amazon SageMaker AI to flag anomalous model invocations, cost harvesting and prompt injection. Detectors that do not report the feature return `MANUAL`, because absence means the Region does not offer it.",
|
||||
"Risk": "Without **AI Protection**, model invocation activity is never baselined, so attackers using **stolen credentials** can invoke foundation models undetected.\n\nThat costs **confidentiality** of prompts and outputs, and **availability** too: expensive prompts harvest inference spend and exhaust quota.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://docs.aws.amazon.com/guardduty/latest/ug/ai-protection.html",
|
||||
"https://docs.aws.amazon.com/guardduty/latest/ug/ai-protection-enable-standalone-account.html",
|
||||
"https://docs.aws.amazon.com/guardduty/latest/ug/findings-ai-protection.html",
|
||||
"https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_regions.html"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws guardduty update-detector --detector-id <detector-id> --features Name=AI_PROTECTION,Status=ENABLED",
|
||||
"NativeIaC": "```yaml\nResources:\n <example_resource_name>:\n Type: AWS::GuardDuty::Detector\n Properties:\n Enable: true\n Features:\n - Name: AI_PROTECTION # Critical: selects the GuardDuty AI Protection plan\n Status: ENABLED # Critical: turns AI Protection on\n```",
|
||||
"Other": "1. Open the AWS Console and go to Amazon GuardDuty\n2. In the Region selector, choose a Region that offers AI Protection\n3. In the navigation pane, choose Protection plans\n4. Choose Configure all enablements, then under AI Protection choose Enable\n5. Choose Save all, then Confirm and save\n6. In an organization, do this from the delegated GuardDuty administrator account and auto-enable it for new accounts",
|
||||
"Terraform": "```hcl\nresource \"aws_guardduty_detector_feature\" \"<example_resource_name>\" {\n detector_id = aws_guardduty_detector.<example_detector_name>.id\n name = \"AI_PROTECTION\" # Critical: GuardDuty AI Protection plan\n status = \"ENABLED\" # Critical: enable the feature\n}\n```"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Enable **GuardDuty AI Protection** in every account and Region hosting AI workloads.\n- Enable it org-wide from the delegated GuardDuty administrator and auto-enable it for new accounts\n- Enforce **Amazon Bedrock Guardrails** for prompt attacks, which AI Protection requires to raise prompt injection findings\n- Route findings to AWS Security Hub and review them on a defined cadence",
|
||||
"Url": "https://hub.prowler.com/check/guardduty_ai_protection_enabled"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.providers.aws.services.guardduty.guardduty_client import guardduty_client
|
||||
|
||||
|
||||
class guardduty_ai_protection_enabled(Check):
|
||||
"""Ensure GuardDuty AI Protection is enabled on every active detector.
|
||||
|
||||
AI Protection analyzes AWS CloudTrail data events from Amazon Bedrock, Amazon
|
||||
Bedrock AgentCore and Amazon SageMaker AI, which makes it the detective control
|
||||
for AI workloads.
|
||||
|
||||
The feature has three observable states rather than two:
|
||||
|
||||
1. Reported as ENABLED: PASS.
|
||||
2. Reported as DISABLED: FAIL.
|
||||
3. Not reported at all: MANUAL. GuardDuty omits features that the Region or the
|
||||
GuardDuty version does not offer, and "could not tell" is not "not
|
||||
compliant". The same account can carry a feature in some Regions and omit it
|
||||
in others, so absence cannot be read as disablement.
|
||||
|
||||
A suspended detector, or one whose GetDetector call failed, is MANUAL as well:
|
||||
the feature state is unknown either way, and guardduty_is_enabled owns the
|
||||
detector-level finding. Regions with no detector at all are left to
|
||||
guardduty_is_enabled entirely.
|
||||
"""
|
||||
|
||||
def execute(self) -> list[Check_Report_AWS]:
|
||||
"""Assess AI Protection on every GuardDuty detector in the account.
|
||||
|
||||
Returns:
|
||||
list[Check_Report_AWS]: one report per detector that exists. PASS when AI
|
||||
Protection is enabled, FAIL when GuardDuty reported the feature
|
||||
disabled, and MANUAL when either the detector state or the feature
|
||||
itself was not reported.
|
||||
"""
|
||||
findings = []
|
||||
for detector in guardduty_client.detectors:
|
||||
if not detector.enabled_in_account:
|
||||
continue
|
||||
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=detector)
|
||||
|
||||
if not detector.status:
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"GuardDuty detector {detector.id} is not enabled or could not be read, so AI Protection coverage could not be determined."
|
||||
elif detector.ai_protection is None:
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"GuardDuty detector {detector.id} does not report the AI Protection feature, so verify manually whether AI Protection is available in region {detector.region}."
|
||||
elif detector.ai_protection:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"GuardDuty detector {detector.id} has AI Protection enabled."
|
||||
)
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"GuardDuty detector {detector.id} does not have AI Protection enabled."
|
||||
|
||||
findings.append(report)
|
||||
return findings
|
||||
+2
-2
@@ -22,10 +22,10 @@
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws guardduty update-detector --detector-id <detector-id> --features name=EKS_RUNTIME_MONITORING,status=ENABLED",
|
||||
"CLI": "aws guardduty update-detector --detector-id <detector-id> --features Name=EKS_RUNTIME_MONITORING,Status=ENABLED",
|
||||
"NativeIaC": "```yaml\nResources:\n <example_resource_name>:\n Type: AWS::GuardDuty::Detector\n Properties:\n Enable: true\n Features:\n - Name: EKS_RUNTIME_MONITORING # Critical: selects EKS Runtime Monitoring feature\n Status: ENABLED # Critical: enables the feature to pass the check\n```",
|
||||
"Other": "1. Open the AWS Console and go to Amazon GuardDuty\n2. In the left pane, select Settings > Runtime monitoring\n3. Under EKS Runtime Monitoring, switch the status to Enabled\n4. Click Save changes",
|
||||
"Terraform": "```hcl\nresource \"aws_guardduty_detector\" \"<example_resource_name>\" {\n enable = true\n\n features {\n name = \"EKS_RUNTIME_MONITORING\" # Critical: selects EKS Runtime Monitoring feature\n status = \"ENABLED\" # Critical: enables the feature to pass the check\n }\n}\n```"
|
||||
"Terraform": "```hcl\nresource \"aws_guardduty_detector\" \"<example_resource_name>\" {\n enable = true\n}\n\nresource \"aws_guardduty_detector_feature\" \"<example_resource_name>\" {\n detector_id = aws_guardduty_detector.<example_resource_name>.id\n name = \"EKS_RUNTIME_MONITORING\" # Critical: selects EKS Runtime Monitoring\n status = \"ENABLED\" # Critical: enables the feature\n\n additional_configuration {\n name = \"EKS_ADDON_MANAGEMENT\"\n status = \"ENABLED\"\n }\n}\n```"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "- Enable **EKS Runtime Monitoring** with automated agent management across all accounts and clusters\n- Enforce **least privilege** for agents and segment cluster access\n- Integrate findings with response workflows and periodically verify runtime coverage",
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "guardduty_runtime_monitoring_enabled",
|
||||
"CheckTitle": "GuardDuty detector has Runtime Monitoring enabled",
|
||||
"CheckType": [
|
||||
"Software and Configuration Checks/AWS Security Best Practices/Runtime Behavior Analysis",
|
||||
"Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices"
|
||||
],
|
||||
"ServiceName": "guardduty",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AwsGuardDutyDetector",
|
||||
"ResourceGroup": "security",
|
||||
"Description": "GuardDuty detectors are evaluated for unified **Runtime Monitoring** being enabled. The configuration is at the detector level and relates to visibility into *process execution, file access, and network connections* on Amazon EC2 instances, Amazon ECS on AWS Fargate tasks, and Amazon EKS nodes and containers. The legacy EKS-only feature covers Amazon EKS alone and does not satisfy this check.",
|
||||
"Risk": "Without **Runtime Monitoring**, on-host behavior of EC2, Fargate and EKS workloads is blind to detection. Adversaries can run malware or cryptominers, break out of containers, harvest credentials from instance metadata, tamper with workloads, or pivot to other services, degrading confidentiality, corrupting integrity, and exhausting resources (availability).",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://docs.aws.amazon.com/guardduty/latest/ug/runtime-monitoring.html",
|
||||
"https://docs.aws.amazon.com/guardduty/latest/ug/runtime-monitoring-configuration.html",
|
||||
"https://docs.aws.amazon.com/config/latest/developerguide/guardduty-runtime-monitoring-enabled.html",
|
||||
"https://docs.aws.amazon.com/securityhub/latest/userguide/guardduty-controls.html#guardduty-11"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws guardduty update-detector --detector-id <detector-id> --features Name=RUNTIME_MONITORING,Status=ENABLED",
|
||||
"NativeIaC": "```yaml\nResources:\n <example_resource_name>:\n Type: AWS::GuardDuty::Detector\n Properties:\n Enable: true\n Features:\n - Name: RUNTIME_MONITORING # Critical: selects unified Runtime Monitoring, which covers EC2, ECS-Fargate and EKS\n Status: ENABLED # Critical: enables the feature to pass the check\n AdditionalConfiguration:\n - Name: EC2_AGENT_MANAGEMENT\n Status: ENABLED\n - Name: ECS_FARGATE_AGENT_MANAGEMENT\n Status: ENABLED\n - Name: EKS_ADDON_MANAGEMENT\n Status: ENABLED\n```",
|
||||
"Other": "1. Open the AWS Console and go to Amazon GuardDuty\n2. In the left pane, select Protection plans > Runtime Monitoring\n3. Switch Runtime Monitoring to Enabled\n4. Enable automated agent configuration for Amazon EC2, AWS Fargate (Amazon ECS only) and Amazon EKS\n5. Click Save changes\n6. If you were using EKS Runtime Monitoring, migrate to Runtime Monitoring; the two features are mutually exclusive",
|
||||
"Terraform": "```hcl\nresource \"aws_guardduty_detector_feature\" \"<example_resource_name>\" {\n detector_id = aws_guardduty_detector.<example_detector_name>.id\n name = \"RUNTIME_MONITORING\" # Critical: unified feature covering EC2, ECS-Fargate and EKS\n status = \"ENABLED\" # Critical: enables the feature to pass the check\n\n additional_configuration {\n name = \"EC2_AGENT_MANAGEMENT\"\n status = \"ENABLED\"\n }\n\n additional_configuration {\n name = \"ECS_FARGATE_AGENT_MANAGEMENT\"\n status = \"ENABLED\"\n }\n\n additional_configuration {\n name = \"EKS_ADDON_MANAGEMENT\"\n status = \"ENABLED\"\n }\n}\n```"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "- Enable unified **Runtime Monitoring** with automated agent management for Amazon EC2, AWS Fargate (Amazon ECS only) and Amazon EKS across all accounts\n- Migrate from EKS Runtime Monitoring, which covers Amazon EKS only and is mutually exclusive with Runtime Monitoring\n- Review runtime coverage statistics rather than treating enablement as coverage, and route findings to Security Hub or EventBridge for response",
|
||||
"Url": "https://hub.prowler.com/check/guardduty_runtime_monitoring_enabled"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.providers.aws.services.guardduty.guardduty_client import guardduty_client
|
||||
|
||||
|
||||
class guardduty_runtime_monitoring_enabled(Check):
|
||||
"""Ensure GuardDuty unified Runtime Monitoring is enabled on every active detector.
|
||||
|
||||
Runtime Monitoring covers Amazon EC2 instances, Amazon ECS on AWS Fargate tasks
|
||||
and Amazon EKS nodes and containers. Legacy EKS Runtime Monitoring covers Amazon
|
||||
EKS alone, and its AdditionalConfiguration offers no EC2 or Fargate agent
|
||||
management, so a detector running only the legacy feature has no runtime coverage
|
||||
for EC2 or Fargate workloads and cannot PASS. The two features are mutually
|
||||
exclusive at the API, so the FAIL message names the legacy case to point at
|
||||
migration rather than at first-time enablement. That exclusivity is also why the
|
||||
legacy verdict is reached before the unknown one: a legacy detector is precisely
|
||||
the one whose GetDetector response carries no RUNTIME_MONITORING entry, so testing
|
||||
for the unknown state first would report every legacy detector as undetermined.
|
||||
|
||||
A detector whose own state could not be read is MANUAL rather than absent from the
|
||||
report. Detector.status is True only when GetDetector returned ENABLED and stays
|
||||
None both for a suspended detector and for a GetDetector call that failed, so those
|
||||
two cannot be told apart and neither is a definite absence of runtime coverage.
|
||||
Leaving such a detector out of the findings would leave its Region with nothing to
|
||||
read at all, and an unreported Region reads as a compliant one.
|
||||
|
||||
Regions with no detector at all are left to guardduty_is_enabled entirely, which is
|
||||
also the check that owns the detector-level verdict.
|
||||
|
||||
guardduty_eks_runtime_monitoring_enabled remains the EKS-scoped check.
|
||||
"""
|
||||
|
||||
def execute(self) -> list[Check_Report_AWS]:
|
||||
"""Assess unified Runtime Monitoring on every GuardDuty detector in the account.
|
||||
|
||||
Returns:
|
||||
list[Check_Report_AWS]: one report per detector that exists. PASS when
|
||||
unified Runtime Monitoring is enabled, FAIL when GuardDuty reported the
|
||||
feature disabled or reported only the legacy EKS one, and MANUAL when
|
||||
either the detector state or the feature itself was not reported and no
|
||||
legacy coverage was reported either.
|
||||
"""
|
||||
findings = []
|
||||
for detector in guardduty_client.detectors:
|
||||
if not detector.enabled_in_account:
|
||||
continue
|
||||
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=detector)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"GuardDuty detector {detector.id} does not have Runtime Monitoring enabled."
|
||||
if not detector.status:
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"GuardDuty detector {detector.id} is not enabled or could not be read, so Runtime Monitoring coverage could not be determined."
|
||||
elif detector.runtime_monitoring is True:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"GuardDuty detector {detector.id} has Runtime Monitoring enabled."
|
||||
)
|
||||
elif detector.eks_runtime_monitoring:
|
||||
# Ordered ahead of the unknown branch below because a detector running the
|
||||
# legacy feature is the shape that omits RUNTIME_MONITORING entirely: the
|
||||
# two are mutually exclusive at the API. eks_runtime_monitoring is set by
|
||||
# either feature, but the PASS branch above already took the unified case,
|
||||
# so reaching here means EKS_RUNTIME_MONITORING is what enabled it. That is
|
||||
# a known absence of EC2 and Fargate coverage, not an unknown one.
|
||||
report.status_extended = f"GuardDuty detector {detector.id} only has the legacy EKS Runtime Monitoring enabled, leaving Amazon EC2 instances and Amazon ECS on Fargate tasks without runtime coverage."
|
||||
elif detector.runtime_monitoring is None:
|
||||
# GetDetector did not return RUNTIME_MONITORING at all, which is not the
|
||||
# same as returning it DISABLED: a Region that does not offer the unified
|
||||
# feature, or a features array that could not be read, would otherwise be
|
||||
# reported as a definite FAIL. No legacy coverage was reported either, so
|
||||
# nothing is known about this detector's runtime coverage.
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = f"GuardDuty detector {detector.id} did not report the Runtime Monitoring feature, so runtime coverage could not be determined; verify manually."
|
||||
findings.append(report)
|
||||
return findings
|
||||
@@ -63,6 +63,15 @@ class GuardDuty(AWSService):
|
||||
)
|
||||
|
||||
def _get_detector(self, detector):
|
||||
"""Read a detector's status, data sources and features.
|
||||
|
||||
A feature GuardDuty does not return is left as None rather than False, so a
|
||||
Region that does not offer the feature stays distinguishable from one that
|
||||
turned it off.
|
||||
|
||||
Args:
|
||||
detector: Detector object to populate in place.
|
||||
"""
|
||||
logger.info("GuardDuty - getting detector info...")
|
||||
try:
|
||||
if detector.id and detector.enabled_in_account:
|
||||
@@ -108,11 +117,33 @@ class GuardDuty(AWSService):
|
||||
and feat.get("Status", "DISABLED") == "ENABLED"
|
||||
):
|
||||
detector.lambda_protection = True
|
||||
elif (
|
||||
feat.get("Name", "") == "EKS_RUNTIME_MONITORING"
|
||||
and feat.get("Status", "DISABLED") == "ENABLED"
|
||||
elif feat.get("Name", "") == "AI_PROTECTION":
|
||||
# Recorded even when DISABLED, so a Region that offers AI
|
||||
# Protection and turned it off stays distinguishable from one
|
||||
# that never reports the feature.
|
||||
detector.ai_protection = (
|
||||
feat.get("Status", "DISABLED") == "ENABLED"
|
||||
)
|
||||
elif feat.get("Name", "") in (
|
||||
"EKS_RUNTIME_MONITORING",
|
||||
"RUNTIME_MONITORING",
|
||||
):
|
||||
detector.eks_runtime_monitoring = True
|
||||
enabled = feat.get("Status", "DISABLED") == "ENABLED"
|
||||
# Unified Runtime Monitoring (RUNTIME_MONITORING) already
|
||||
# includes threat detection for Amazon EKS resources and is
|
||||
# mutually exclusive with EKS_RUNTIME_MONITORING, so either
|
||||
# feature means the detector has EKS runtime coverage.
|
||||
if enabled:
|
||||
detector.eks_runtime_monitoring = True
|
||||
if feat.get("Name", "") == "RUNTIME_MONITORING":
|
||||
# Only the unified feature covers Amazon EC2 and Amazon
|
||||
# ECS on Fargate, so it is tracked separately. Recorded even
|
||||
# when DISABLED, for the same reason AI_PROTECTION above is:
|
||||
# a Region that offers the feature and turned it off must
|
||||
# stay distinguishable from one that never reported it. A
|
||||
# plain bool cannot express that, and the check would report
|
||||
# a definite FAIL on a Region that has no unified feature.
|
||||
detector.runtime_monitoring = enabled
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
@@ -345,8 +376,12 @@ class Detector(BaseModel):
|
||||
rds_protection: bool = False
|
||||
eks_audit_log_protection: bool = False
|
||||
eks_runtime_monitoring: bool = False
|
||||
# None when GuardDuty did not return the feature: unknown, not disabled.
|
||||
runtime_monitoring: Optional[bool] = None
|
||||
lambda_protection: bool = False
|
||||
ec2_malware_protection: bool = False
|
||||
# None when GuardDuty did not return the feature: unknown, not disabled.
|
||||
ai_protection: Optional[bool] = None
|
||||
# Organization configuration fields
|
||||
organization_auto_enable_members: str = "NONE" # NEW, ALL, or NONE
|
||||
organization_config_available: bool = False
|
||||
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
from unittest import mock
|
||||
|
||||
import botocore
|
||||
from boto3 import client
|
||||
from moto import mock_aws
|
||||
|
||||
from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty
|
||||
from tests.providers.aws.utils import (
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_REGION_US_EAST_1,
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
CHECK_CLIENT_PATH = "prowler.providers.aws.services.guardduty.guardduty_ai_protection_enabled.guardduty_ai_protection_enabled.guardduty_client"
|
||||
|
||||
make_api_call = botocore.client.BaseClient._make_api_call
|
||||
|
||||
|
||||
def mock_get_detector_raises(self, operation_name, kwarg):
|
||||
"""Deny GetDetector only, leaving every other GuardDuty operation intact."""
|
||||
if operation_name == "GetDetector":
|
||||
raise botocore.exceptions.ClientError(
|
||||
{"Error": {"Code": "AccessDeniedException", "Message": "denied"}},
|
||||
operation_name,
|
||||
)
|
||||
return make_api_call(self, operation_name, kwarg)
|
||||
|
||||
|
||||
def _run_check(aws_provider):
|
||||
"""Run the check against a GuardDuty service built from the mocked provider."""
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(CHECK_CLIENT_PATH, new=GuardDuty(aws_provider)),
|
||||
):
|
||||
from prowler.providers.aws.services.guardduty.guardduty_ai_protection_enabled.guardduty_ai_protection_enabled import (
|
||||
guardduty_ai_protection_enabled,
|
||||
)
|
||||
|
||||
return guardduty_ai_protection_enabled().execute()
|
||||
|
||||
|
||||
class Test_guardduty_ai_protection_enabled:
|
||||
@mock_aws
|
||||
def test_no_detectors(self):
|
||||
"""A Region with no detector has no resource to judge; guardduty_is_enabled owns it."""
|
||||
client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_ai_protection_enabled(self):
|
||||
"""An enabled feature PASSes and carries the detector's own resource fields."""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[{"Name": "AI_PROTECTION", "Status": "ENABLED"}],
|
||||
)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} has AI Protection enabled."
|
||||
)
|
||||
assert result[0].resource_id == response["DetectorId"]
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert (
|
||||
result[0].resource_arn
|
||||
== f"arn:aws:guardduty:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:detector/{response['DetectorId']}"
|
||||
)
|
||||
assert result[0].resource_tags == []
|
||||
|
||||
@mock_aws
|
||||
def test_ai_protection_disabled(self):
|
||||
"""A reported-disabled feature FAILs and names the detector."""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[{"Name": "AI_PROTECTION", "Status": "DISABLED"}],
|
||||
)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} does not have AI Protection enabled."
|
||||
)
|
||||
assert result[0].resource_id == response["DetectorId"]
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
|
||||
@mock_aws
|
||||
def test_ai_protection_feature_absent(self):
|
||||
"""A feature GuardDuty does not report is not a feature GuardDuty turned off.
|
||||
|
||||
The Region or the GuardDuty version may not offer AI Protection at all.
|
||||
"""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[
|
||||
{"Name": "S3_DATA_EVENTS", "Status": "ENABLED"},
|
||||
{"Name": "LAMBDA_NETWORK_LOGS", "Status": "ENABLED"},
|
||||
],
|
||||
)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} does not report the AI Protection feature, so verify manually whether AI Protection is available in region {AWS_REGION_US_EAST_1}."
|
||||
)
|
||||
assert result[0].resource_id == response["DetectorId"]
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
|
||||
@mock_aws
|
||||
def test_no_features_reported(self):
|
||||
"""An empty features array reads the same as an absent AI_PROTECTION entry."""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(Enable=True)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} does not report the AI Protection feature, so verify manually whether AI Protection is available in region {AWS_REGION_US_EAST_1}."
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_detector_not_enabled(self):
|
||||
"""A suspended detector is MANUAL: its feature state is unknown, not absent."""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=False,
|
||||
Features=[{"Name": "AI_PROTECTION", "Status": "ENABLED"}],
|
||||
)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} is not enabled or could not be read, so AI Protection coverage could not be determined."
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_get_detector_unreadable(self):
|
||||
"""A denied GetDetector is unknown, not absent: MANUAL instead of FAIL."""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[{"Name": "AI_PROTECTION", "Status": "DISABLED"}],
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
with mock.patch(
|
||||
"botocore.client.BaseClient._make_api_call", new=mock_get_detector_raises
|
||||
):
|
||||
result = _run_check(aws_provider)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} is not enabled or could not be read, so AI Protection coverage could not be determined."
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_real_get_detector_payload_ai_protection_disabled(self):
|
||||
"""The real GetDetector payload carries AI_PROTECTION alongside AI_ANALYST.
|
||||
|
||||
AI_PROTECTION is absent from the pinned DetectorFeatureResult enum, so this
|
||||
asserts the name still reaches the check alongside the two runtime feature names.
|
||||
"""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[
|
||||
{"Name": "CLOUD_TRAIL", "Status": "ENABLED"},
|
||||
{"Name": "DNS_LOGS", "Status": "ENABLED"},
|
||||
{"Name": "FLOW_LOGS", "Status": "ENABLED"},
|
||||
{"Name": "S3_DATA_EVENTS", "Status": "ENABLED"},
|
||||
{"Name": "EKS_AUDIT_LOGS", "Status": "ENABLED"},
|
||||
{"Name": "EBS_MALWARE_PROTECTION", "Status": "ENABLED"},
|
||||
{"Name": "RDS_LOGIN_EVENTS", "Status": "ENABLED"},
|
||||
{"Name": "AI_PROTECTION", "Status": "DISABLED"},
|
||||
{"Name": "AI_ANALYST", "Status": "ENABLED"},
|
||||
{"Name": "EKS_RUNTIME_MONITORING", "Status": "DISABLED"},
|
||||
{"Name": "LAMBDA_NETWORK_LOGS", "Status": "ENABLED"},
|
||||
{"Name": "RUNTIME_MONITORING", "Status": "DISABLED"},
|
||||
],
|
||||
)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} does not have AI Protection enabled."
|
||||
)
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
from unittest import mock
|
||||
|
||||
import botocore
|
||||
from boto3 import client
|
||||
from moto import mock_aws
|
||||
|
||||
from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty
|
||||
from tests.providers.aws.utils import (
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_REGION_US_EAST_1,
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
CHECK_CLIENT_PATH = "prowler.providers.aws.services.guardduty.guardduty_runtime_monitoring_enabled.guardduty_runtime_monitoring_enabled.guardduty_client"
|
||||
|
||||
make_api_call = botocore.client.BaseClient._make_api_call
|
||||
|
||||
|
||||
def mock_get_detector_raises(self, operation_name, kwarg):
|
||||
"""Deny GetDetector only, leaving every other GuardDuty operation intact."""
|
||||
if operation_name == "GetDetector":
|
||||
raise botocore.exceptions.ClientError(
|
||||
{"Error": {"Code": "AccessDeniedException", "Message": "denied"}},
|
||||
operation_name,
|
||||
)
|
||||
return make_api_call(self, operation_name, kwarg)
|
||||
|
||||
|
||||
def _run_check(aws_provider):
|
||||
"""Run the check against a GuardDuty service built from the mocked provider."""
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(CHECK_CLIENT_PATH, new=GuardDuty(aws_provider)),
|
||||
):
|
||||
from prowler.providers.aws.services.guardduty.guardduty_runtime_monitoring_enabled.guardduty_runtime_monitoring_enabled import (
|
||||
guardduty_runtime_monitoring_enabled,
|
||||
)
|
||||
|
||||
return guardduty_runtime_monitoring_enabled().execute()
|
||||
|
||||
|
||||
class Test_guardduty_runtime_monitoring_enabled:
|
||||
@mock_aws
|
||||
def test_no_detectors(self):
|
||||
"""A Region with no detector has no resource to judge; guardduty_is_enabled owns it."""
|
||||
client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_detector_disabled(self):
|
||||
"""A suspended detector is MANUAL, never dropped.
|
||||
|
||||
The detector exists, so omitting it would leave the Region unreported, which
|
||||
reads as compliant -- and it is reported here with Runtime Monitoring ENABLED,
|
||||
the case where the omission was hardest to notice.
|
||||
"""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=False,
|
||||
Features=[{"Name": "RUNTIME_MONITORING", "Status": "ENABLED"}],
|
||||
)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} is not enabled or could not be read, so Runtime Monitoring coverage could not be determined."
|
||||
)
|
||||
assert result[0].resource_id == response["DetectorId"]
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
|
||||
@mock_aws
|
||||
def test_get_detector_unreadable(self):
|
||||
"""A denied GetDetector is unknown, not absent: MANUAL instead of FAIL.
|
||||
|
||||
Detector.status cannot distinguish this from a suspended detector, which is why
|
||||
both carry the same MANUAL wording rather than a definite verdict.
|
||||
"""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[{"Name": "RUNTIME_MONITORING", "Status": "DISABLED"}],
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
with mock.patch(
|
||||
"botocore.client.BaseClient._make_api_call", new=mock_get_detector_raises
|
||||
):
|
||||
result = _run_check(aws_provider)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} is not enabled or could not be read, so Runtime Monitoring coverage could not be determined."
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_runtime_monitoring_enabled(self):
|
||||
"""An enabled unified feature PASSes and carries the detector's own resource fields."""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[{"Name": "RUNTIME_MONITORING", "Status": "ENABLED"}],
|
||||
)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} has Runtime Monitoring enabled."
|
||||
)
|
||||
assert result[0].resource_id == response["DetectorId"]
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert (
|
||||
result[0].resource_arn
|
||||
== f"arn:aws:guardduty:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:detector/{response['DetectorId']}"
|
||||
)
|
||||
assert result[0].resource_tags == []
|
||||
|
||||
@mock_aws
|
||||
def test_runtime_monitoring_disabled(self):
|
||||
"""A reported-disabled unified feature FAILs and names the detector."""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[{"Name": "RUNTIME_MONITORING", "Status": "DISABLED"}],
|
||||
)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} does not have Runtime Monitoring enabled."
|
||||
)
|
||||
assert result[0].resource_id == response["DetectorId"]
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
|
||||
@mock_aws
|
||||
def test_unreported_runtime_feature_is_manual_not_fail(self):
|
||||
"""A feature the detector never reports is not the same as one it reports DISABLED.
|
||||
|
||||
GuardDuty returns a disabled feature with Status DISABLED rather than omitting
|
||||
it -- which is exactly why AI_PROTECTION is recorded even when off -- so an
|
||||
omitted RUNTIME_MONITORING means the Region does not offer the unified feature,
|
||||
or the features array could not be read. Neither is a definite absence of
|
||||
runtime coverage.
|
||||
"""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[{"Name": "S3_DATA_EVENTS", "Status": "ENABLED"}],
|
||||
)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} did not report the Runtime Monitoring feature, so runtime coverage could not be determined; verify manually."
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_runtime_monitoring_reported_disabled_still_fails(self):
|
||||
"""The complement of the unreported case: reported and DISABLED stays a FAIL.
|
||||
|
||||
Making the unreported case MANUAL must not soften a definite absence of coverage.
|
||||
"""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[{"Name": "RUNTIME_MONITORING", "Status": "DISABLED"}],
|
||||
)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} does not have Runtime Monitoring enabled."
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_legacy_eks_runtime_monitoring_only_fails(self):
|
||||
"""The legacy EKS-only feature covers Amazon EKS and nothing else.
|
||||
|
||||
It must not PASS a check about Amazon EC2 and Amazon ECS on Fargate coverage.
|
||||
"""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[
|
||||
{"Name": "EKS_RUNTIME_MONITORING", "Status": "ENABLED"},
|
||||
{"Name": "RUNTIME_MONITORING", "Status": "DISABLED"},
|
||||
],
|
||||
)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} only has the legacy EKS Runtime Monitoring enabled, leaving Amazon EC2 instances and Amazon ECS on Fargate tasks without runtime coverage."
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_legacy_eks_only_without_any_unified_entry_fails(self):
|
||||
"""The real legacy shape: the unified feature is absent, not reported DISABLED.
|
||||
|
||||
The two features are mutually exclusive at the API, so a detector on the legacy
|
||||
one has no RUNTIME_MONITORING entry at all -- which is the same absence that makes
|
||||
an unknown detector MANUAL. The test above supplies a DISABLED unified entry as
|
||||
well, so it never reaches that ambiguity. Here the legacy verdict has to win on
|
||||
ordering alone: EKS coverage is stated, so EC2 and Fargate are definitively
|
||||
uncovered rather than undetermined.
|
||||
"""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[{"Name": "EKS_RUNTIME_MONITORING", "Status": "ENABLED"}],
|
||||
)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} only has the legacy EKS Runtime Monitoring enabled, leaving Amazon EC2 instances and Amazon ECS on Fargate tasks without runtime coverage."
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_unified_enabled_with_legacy_disabled(self):
|
||||
"""GetDetector returns an entry for both feature names on the same detector.
|
||||
|
||||
A DISABLED legacy feature must not mask the ENABLED unified one.
|
||||
"""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_US_EAST_1)
|
||||
response = guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[
|
||||
{"Name": "EKS_RUNTIME_MONITORING", "Status": "DISABLED"},
|
||||
{"Name": "RUNTIME_MONITORING", "Status": "ENABLED"},
|
||||
],
|
||||
)
|
||||
|
||||
result = _run_check(set_mocked_aws_provider([AWS_REGION_US_EAST_1]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"GuardDuty detector {response['DetectorId']} has Runtime Monitoring enabled."
|
||||
)
|
||||
@@ -2,6 +2,7 @@ from datetime import datetime
|
||||
from unittest.mock import patch
|
||||
|
||||
import botocore
|
||||
import pytest
|
||||
from boto3 import client
|
||||
from moto import mock_aws
|
||||
|
||||
@@ -143,6 +144,142 @@ class Test_GuardDuty_Service:
|
||||
assert guardduty.detectors[0].region == AWS_REGION_EU_WEST_1
|
||||
assert guardduty.detectors[0].tags == [{"test": "test"}]
|
||||
|
||||
@mock_aws
|
||||
@pytest.mark.parametrize(
|
||||
"feature_name", ["EKS_RUNTIME_MONITORING", "RUNTIME_MONITORING"]
|
||||
)
|
||||
def test_get_detector_eks_runtime_monitoring(self, feature_name):
|
||||
"""Both feature names set eks_runtime_monitoring.
|
||||
|
||||
Unified Runtime Monitoring supersedes EKS Runtime Monitoring and covers EKS.
|
||||
"""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1)
|
||||
guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[{"Name": feature_name, "Status": "ENABLED"}],
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
guardduty = GuardDuty(aws_provider)
|
||||
|
||||
assert len(guardduty.detectors) == 1
|
||||
assert guardduty.detectors[0].eks_runtime_monitoring
|
||||
|
||||
@mock_aws
|
||||
@pytest.mark.parametrize(
|
||||
"feature_name", ["EKS_RUNTIME_MONITORING", "RUNTIME_MONITORING"]
|
||||
)
|
||||
def test_get_detector_eks_runtime_monitoring_disabled(self, feature_name):
|
||||
"""Neither feature name sets eks_runtime_monitoring while it is DISABLED."""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1)
|
||||
guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[{"Name": feature_name, "Status": "DISABLED"}],
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
guardduty = GuardDuty(aws_provider)
|
||||
|
||||
assert len(guardduty.detectors) == 1
|
||||
assert not guardduty.detectors[0].eks_runtime_monitoring
|
||||
|
||||
@mock_aws
|
||||
def test_get_detector_unified_runtime_monitoring_with_disabled_eks_feature(self):
|
||||
"""GetDetector returns an entry for both feature names.
|
||||
|
||||
The DISABLED legacy feature must not mask the ENABLED unified one regardless of
|
||||
the order they arrive in.
|
||||
"""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1)
|
||||
guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[
|
||||
{"Name": "EKS_RUNTIME_MONITORING", "Status": "DISABLED"},
|
||||
{"Name": "RUNTIME_MONITORING", "Status": "ENABLED"},
|
||||
],
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
guardduty = GuardDuty(aws_provider)
|
||||
|
||||
assert len(guardduty.detectors) == 1
|
||||
assert guardduty.detectors[0].eks_runtime_monitoring
|
||||
|
||||
@mock_aws
|
||||
def test_get_detector_runtime_monitoring_is_unified_only(self):
|
||||
"""The legacy EKS feature must not set runtime_monitoring.
|
||||
|
||||
Only the unified feature covers Amazon EC2 and Amazon ECS on Fargate.
|
||||
"""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1)
|
||||
guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[{"Name": "EKS_RUNTIME_MONITORING", "Status": "ENABLED"}],
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
guardduty = GuardDuty(aws_provider)
|
||||
|
||||
assert len(guardduty.detectors) == 1
|
||||
assert guardduty.detectors[0].eks_runtime_monitoring
|
||||
assert not guardduty.detectors[0].runtime_monitoring
|
||||
|
||||
@mock_aws
|
||||
@pytest.mark.parametrize("status", ["ENABLED", "DISABLED"])
|
||||
def test_get_detector_runtime_monitoring(self, status):
|
||||
"""runtime_monitoring tracks the reported status of the unified feature."""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1)
|
||||
guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[{"Name": "RUNTIME_MONITORING", "Status": status}],
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
guardduty = GuardDuty(aws_provider)
|
||||
|
||||
assert len(guardduty.detectors) == 1
|
||||
assert guardduty.detectors[0].runtime_monitoring == (status == "ENABLED")
|
||||
|
||||
@mock_aws
|
||||
@pytest.mark.parametrize(
|
||||
"status, expected", [("ENABLED", True), ("DISABLED", False)]
|
||||
)
|
||||
def test_get_detector_ai_protection(self, status, expected):
|
||||
"""ai_protection is recorded as a bool for both reported statuses."""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1)
|
||||
guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[{"Name": "AI_PROTECTION", "Status": status}],
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
guardduty = GuardDuty(aws_provider)
|
||||
|
||||
assert len(guardduty.detectors) == 1
|
||||
assert guardduty.detectors[0].ai_protection is expected
|
||||
|
||||
@mock_aws
|
||||
def test_get_detector_ai_protection_absent_stays_none(self):
|
||||
"""An AI_PROTECTION entry GuardDuty never returned must stay None.
|
||||
|
||||
That is what lets a check tell a Region without AI Protection apart from a
|
||||
Region that offers the feature and disabled it.
|
||||
"""
|
||||
guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1)
|
||||
guardduty_client.create_detector(
|
||||
Enable=True,
|
||||
Features=[
|
||||
{"Name": "S3_DATA_EVENTS", "Status": "ENABLED"},
|
||||
{"Name": "AI_ANALYST", "Status": "ENABLED"},
|
||||
],
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
guardduty = GuardDuty(aws_provider)
|
||||
|
||||
assert len(guardduty.detectors) == 1
|
||||
assert guardduty.detectors[0].ai_protection is None
|
||||
|
||||
@mock_aws
|
||||
# Test GuardDuty session
|
||||
def test_list_findings(self):
|
||||
|
||||
Reference in New Issue
Block a user