Files
prowler/prowler/config/schema/aws.py
T

405 lines
13 KiB
Python

"""AWS provider config schema.
Bounds on every field are intentionally conservative: they are not the
absolute service maxima but the values that produce a useful security
check. A user is free to keep the built-in default by omitting the key —
out-of-range values are dropped with a warning at SDK runtime, and
rejected at the Prowler App backend.
Whenever an upper bound is uncertain, the cap is set to a value that
still keeps the check meaningful (e.g. a 10-year window for date-based
thresholds) and avoids ints that obviously break downstream maths
(`min_kinesis_stream_retention_hours = 99999`).
"""
from typing import Annotated, Literal, Optional
from pydantic import AfterValidator, Field
from prowler.config.schema.base import ProviderConfigBase
from prowler.config.schema.validators import (
make_dotted_version_validator,
validate_ip_networks,
validate_port_range,
)
# ---- Reusable constants -----------------------------------------------------
# CloudWatch Logs only accepts these retention values (in days). Anything else
# is silently coerced to the next valid value by the API — we reject upfront.
_CLOUDWATCH_RETENTION_DAYS = (
1,
3,
5,
7,
14,
30,
60,
90,
120,
150,
180,
365,
400,
545,
731,
1096,
1827,
2192,
2557,
2922,
3288,
3653,
)
_VALID_CW_RETENTION_LITERAL = Literal[
1,
3,
5,
7,
14,
30,
60,
90,
120,
150,
180,
365,
400,
545,
731,
1096,
1827,
2192,
2557,
2922,
3288,
3653,
]
# ---- Custom validators ------------------------------------------------------
# Reusable validators shared across providers (see schema/validators.py).
_validate_port_range = validate_port_range
_validate_trusted_ips = validate_ip_networks
# "1.4.0" style strings (used by Fargate platform versions).
_validate_semver = make_dotted_version_validator(3, 3)
# "1.28" style strings (EKS minor versions).
_validate_eks_minor = make_dotted_version_validator(2, 2)
def _validate_account_ids(v: Optional[list[str]]) -> Optional[list[str]]:
if v is None:
return v
for account_id in v:
if not (account_id.isdigit() and len(account_id) == 12):
raise ValueError(
f"trusted_account_ids entry {account_id!r} is not a 12-digit AWS account id"
)
return v
# ---- Main schema ------------------------------------------------------------
class AWSProviderConfig(ProviderConfigBase):
# --- IAM ---------------------------------------------------------------
mute_non_default_regions: Optional[bool] = None
disallowed_regions: Optional[list[str]] = None
max_unused_access_keys_days: Optional[int] = Field(
default=None,
ge=30,
le=180,
description=(
"Days an IAM user access key can stay unused before being flagged. "
"Range: 30..180 days (CIS AWS 1.13 recommends 45; NIST IA-5 ≤90)."
),
)
max_console_access_days: Optional[int] = Field(
default=None,
ge=30,
le=180,
description=(
"Days an IAM console password can stay unused before being flagged. "
"Range: 30..180 days (CIS AWS 1.12 recommends 45)."
),
)
max_unused_sagemaker_access_days: Optional[int] = Field(
default=None,
ge=7,
le=180,
description=(
"Days a SageMaker user access key can stay unused. Range: 7..180 "
"(SageMaker tokens are usually high-privilege over S3/KMS)."
),
)
# --- EC2 ---------------------------------------------------------------
shodan_api_key: Optional[str] = Field(
default=None,
max_length=512,
description="API key for Shodan lookups on EC2 public IPs.",
)
max_security_group_rules: Optional[int] = Field(
default=None,
ge=1,
le=1000,
description="Max ingress+egress rules per security group. AWS hard limit is 1000.",
)
max_ec2_instance_age_in_days: Optional[int] = Field(
default=None,
ge=1,
le=1095,
description=(
"Days an EC2 instance can run before being flagged as old. "
"Range: 1..1095 (3 years; instances should be refreshed for patching "
"per NIST CM-3 — anything older is a security smell)."
),
)
ec2_allowed_interface_types: Optional[list[str]] = None
ec2_allowed_instance_owners: Optional[list[str]] = None
ec2_high_risk_ports: Annotated[
Optional[list[int]], AfterValidator(_validate_port_range)
] = Field(
default=None,
description="TCP/UDP ports considered high-risk when reachable from the Internet (1..65535; port 0 is reserved).",
)
# --- ECS ---------------------------------------------------------------
fargate_linux_latest_version: Annotated[
Optional[str], AfterValidator(_validate_semver)
] = Field(default=None, description="Fargate Linux platform version (X.Y.Z).")
fargate_windows_latest_version: Annotated[
Optional[str], AfterValidator(_validate_semver)
] = Field(default=None, description="Fargate Windows platform version (X.Y.Z).")
# --- Cross-account trust ----------------------------------------------
trusted_account_ids: Annotated[
Optional[list[str]], AfterValidator(_validate_account_ids)
] = Field(
default=None,
description="Additional 12-digit AWS account IDs trusted by cross-account checks.",
)
trusted_ips: Annotated[
Optional[list[str]], AfterValidator(_validate_trusted_ips)
] = Field(
default=None,
description="IPv4/IPv6 addresses or CIDR ranges that are NOT considered public.",
)
# --- CloudWatch / CloudFormation --------------------------------------
log_group_retention_days: Optional[_VALID_CW_RETENTION_LITERAL] = Field(
default=None,
description=(
"Required CloudWatch Logs retention in days. Must match one of the "
f"values accepted by the AWS API: {list(_CLOUDWATCH_RETENTION_DAYS)}."
),
)
recommended_cdk_bootstrap_version: Optional[int] = Field(
default=None,
ge=1,
le=100,
description="Min CDK bootstrap version expected on the account.",
)
# --- AppStream --------------------------------------------------------
max_idle_disconnect_timeout_in_seconds: Optional[int] = Field(
default=None,
ge=60,
le=1800,
description=(
"AppStream idle disconnect timeout (seconds). Range: 60..1800 "
"(NIST AC-12: sensitive sessions ≤15 min — cap at 30 min)."
),
)
max_disconnect_timeout_in_seconds: Optional[int] = Field(
default=None,
ge=60,
le=3600,
description="AppStream disconnect timeout (seconds). Range: 60..3600.",
)
max_session_duration_seconds: Optional[int] = Field(
default=None,
ge=600,
le=86400,
description=(
"AppStream max session duration (seconds). Range: 600..86400 "
"(10 min .. 24 h — AWS AppStream hard limit per session)."
),
)
# --- Lambda -----------------------------------------------------------
obsolete_lambda_runtimes: Optional[list[str]] = None
lambda_min_azs: Optional[int] = Field(
default=None,
ge=1,
le=6,
description="Min number of AZs a VPC-bound Lambda must span. Range: 1..6.",
)
# --- Organizations ----------------------------------------------------
organizations_enabled_regions: Optional[list[str]] = None
organizations_trusted_delegated_administrators: Annotated[
Optional[list[str]], AfterValidator(_validate_account_ids)
] = None
organizations_trusted_ids: Optional[list[str]] = None
# --- ECR --------------------------------------------------------------
ecr_repository_vulnerability_minimum_severity: Optional[
Literal["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFORMATIONAL"]
] = Field(
default=None,
description="Highest severity tolerated for ECR images.",
)
# --- Trusted Advisor --------------------------------------------------
verify_premium_support_plans: Optional[bool] = None
# --- CloudTrail threat detection: privilege escalation ----------------
threat_detection_privilege_escalation_threshold: Optional[float] = Field(
default=None,
ge=0.0,
le=1.0,
description="Fraction of suspicious actions that triggers the priv-esc detection.",
)
threat_detection_privilege_escalation_minutes: Optional[int] = Field(
default=None,
ge=5,
le=43200,
description=(
"Lookback window (minutes) for priv-esc detection. Range: 5..43200 "
"(under 5 min the signal is dominated by false positives)."
),
)
threat_detection_privilege_escalation_actions: Optional[list[str]] = None
# --- CloudTrail threat detection: enumeration -------------------------
threat_detection_enumeration_threshold: Optional[float] = Field(
default=None,
ge=0.0,
le=1.0,
description="Fraction of suspicious actions that triggers the enumeration detection.",
)
threat_detection_enumeration_minutes: Optional[int] = Field(
default=None,
ge=5,
le=43200,
description="Lookback window (minutes) for enumeration detection. Range: 5..43200.",
)
threat_detection_enumeration_actions: Optional[list[str]] = None
# --- CloudTrail threat detection: LLM jacking -------------------------
threat_detection_llm_jacking_threshold: Optional[float] = Field(
default=None,
ge=0.0,
le=1.0,
description="Fraction of suspicious actions that triggers the LLM-jacking detection.",
)
threat_detection_llm_jacking_minutes: Optional[int] = Field(
default=None,
ge=5,
le=43200,
description="Lookback window (minutes) for LLM-jacking detection. Range: 5..43200.",
)
threat_detection_llm_jacking_actions: Optional[list[str]] = None
# --- RDS --------------------------------------------------------------
check_rds_instance_replicas: Optional[bool] = None
# --- ACM --------------------------------------------------------------
days_to_expire_threshold: Optional[int] = Field(
default=None,
ge=7,
le=365,
description=(
"Days before certificate expiration to flag. Range: 7..365 "
"(PCI-DSS 4.2.1.1: alert ≥30 days before expiry; <7 days is too "
"tight to actually act on)."
),
)
insecure_key_algorithms: Optional[list[str]] = None
# --- EKS --------------------------------------------------------------
eks_required_log_types: Optional[
list[
Literal[
"api",
"audit",
"authenticator",
"controllerManager",
"scheduler",
]
]
] = Field(
default=None,
description="EKS control plane log types that must be enabled.",
)
eks_cluster_oldest_version_supported: Annotated[
Optional[str], AfterValidator(_validate_eks_minor)
] = Field(
default=None,
description='Minimum supported EKS minor version, expected as "X.Y".',
)
# --- CodeBuild --------------------------------------------------------
excluded_sensitive_environment_variables: Optional[list[str]] = None
codebuild_github_allowed_organizations: Optional[list[str]] = None
# --- ELB / ELBv2 ------------------------------------------------------
elb_min_azs: Optional[int] = Field(
default=None,
ge=1,
le=6,
description="Min AZs a Classic ELB must span. Range: 1..6.",
)
elbv2_min_azs: Optional[int] = Field(
default=None,
ge=1,
le=6,
description="Min AZs an Application/Network LB must span. Range: 1..6.",
)
# --- ElastiCache -----------------------------------------------------
minimum_snapshot_retention_period: Optional[int] = Field(
default=None,
ge=1,
le=35,
description="Days an ElastiCache backup must be retained. Range: 1..35 (service hard limit).",
)
# --- Secrets ---------------------------------------------------------
secrets_ignore_patterns: Optional[list[str]] = None
secrets_validate: Optional[bool] = Field(
default=None,
description=(
"Validate discovered secrets against the provider APIs (live check). "
"Makes outbound network calls that authenticate with the discovered "
"secret. Disabled by default."
),
)
max_days_secret_unused: Optional[int] = Field(
default=None,
ge=7,
le=365,
description="Days a Secrets Manager secret can stay unused. Range: 7..365.",
)
max_days_secret_unrotated: Optional[int] = Field(
default=None,
ge=1,
le=180,
description=(
"Days a Secrets Manager secret can go without rotation. Range: 1..180 "
"(NIST IA-5: rotate quarterly; CIS recommends ≤90)."
),
)
# --- Kinesis ---------------------------------------------------------
min_kinesis_stream_retention_hours: Optional[int] = Field(
default=None,
ge=24,
le=8760,
description="Hours of Kinesis stream retention. Range: 24..8760 (1 day .. 1 year).",
)