Files
prowler/mcp_server/prowler_mcp_server/prowler_app/tools/resources.py
T

406 lines
18 KiB
Python

"""Cloud Resources tools for Prowler MCP Server.
This module provides tools for searching, viewing, and analyzing cloud resources
across all providers.
"""
from typing import Any
from pydantic import Field
from prowler_mcp_server.prowler_app.models.resources import (
DetailedResource,
ResourceEventsResponse,
ResourcesListResponse,
ResourcesMetadataResponse,
)
from prowler_mcp_server.prowler_app.tools.base import BaseTool
class ResourcesTools(BaseTool):
"""Tools for cloud resources operations.
Provides tools for:
- Searching and filtering cloud resources
- Getting detailed resource information
- Viewing resources overview with statistics
"""
async def list_resources(
self,
provider_type: list[str] = Field(
default=[],
description="Filter by provider type. Multiple values allowed. If empty, all providers are returned. For valid values, please refer to Prowler Hub/Prowler Documentation that you can also find in form of tools in this MCP Server.",
),
provider_alias: str | None = Field(
default=None,
description="Filter by specific provider alias/name (partial match supported). Useful for finding resources in specific accounts like 'production' or 'dev'.",
),
provider_uid: str | None = Field(
default=None,
description="Filter by provider's native ID (e.g., AWS account ID, Azure subscription ID, GCP project ID). All supported provider types are listed in the Prowler Hub/Prowler Documentation that you can also find in form of tools in this MCP Server",
),
region: list[str] = Field(
default=[],
description="Filter by regions. Multiple values allowed (e.g., us-east-1, westus2, europe-west1), format may vary depending on the provider. If empty, all regions are returned.",
),
service: list[str] = Field(
default=[],
description="Filter by service. Multiple values allowed (e.g., s3, ec2, iam, keyvault). If empty, all services are returned.",
),
resource_type: list[str] = Field(
default=[],
description="Filter by resource type. Format may vary depending on the provider. If empty, all resource types are returned.",
),
resource_name: str | None = Field(
default=None,
description="Filter by resource name (partial match supported). Useful for finding specific resources like 'prod-db' or 'test-bucket'.",
),
tag_key: str | None = Field(
default=None,
description="Filter resources by tag key (e.g., 'Environment', 'CostCenter', 'Owner').",
),
tag_value: str | None = Field(
default=None,
description="Filter resources by tag value (e.g., 'production', 'staging', 'development').",
),
date_from: str | None = Field(
default=None,
description="Start date for range query in ISO 8601 format (YYYY-MM-DD, e.g., '2025-01-15'). Full date required. IMPORTANT: Maximum date range is 2 days. If only date_from is provided, date_to is automatically set to 2 days later.",
),
date_to: str | None = Field(
default=None,
description="End date for range query in ISO 8601 format (YYYY-MM-DD, e.g., '2025-01-15'). Full date required. If only date_to is provided, date_from is automatically set to 2 days earlier.",
),
search: str | None = Field(
default=None, description="Free-text search term across resource details"
),
page_size: int = Field(
default=50, description="Number of results to return per page (max 1000)"
),
page_number: int = Field(
default=1, description="Page number to retrieve (1-indexed)"
),
) -> dict[str, Any]:
"""List and filter all resources scanned by Prowler.
IMPORTANT: This tool returns LIGHTWEIGHT resource information. Use this for fast searching
and filtering across many resources. For complete configuration details, metadata, and finding
relationships, use prowler_get_resource on specific resources of interest.
This is the primary tool for browsing resources with rich filtering capabilities.
Returns current state by default (latest scan per provider). Specify dates to query
historical data (2-day maximum window).
Default behavior:
- Returns latest resources from most recent scans (no date parameters needed)
- Returns 50 results per page
- Sorted by service, region, and name for logical grouping
Date filtering:
- Without dates: queries resources from the most recent completed scan per provider (most efficient)
- With dates: queries historical resource state (2-day maximum range between date_from and date_to)
Each resource includes:
- Core identification: id (UUID for prowler_get_resource), uid, name
- Location context: region, service, type
- Security context: failed_findings_count (number of active security issues)
- Tags: tags associated with the resource
Useful Workflow:
1. Use this tool to search and filter resources by provider, region, service, tags, etc.
2. Use prowler_get_resource with the resource 'id' to get complete configuration and metadata
3. Use prowler_search_security_findings to find security issues for specific resources
4. Use prowler_get_finding_details to get details about the security issues for specific resources
"""
# Validate page_size parameter
self.api_client.validate_page_size(page_size)
# Determine endpoint based on date parameters
date_range = self.api_client.normalize_date_range(
date_from, date_to, max_days=2
)
if date_range is None:
# No dates provided - use latest resources endpoint
endpoint = "/resources/latest"
params = {}
else:
# Dates provided - use historical resources endpoint
endpoint = "/resources"
params = {
"filter[updated_at__gte]": date_range[0],
"filter[updated_at__lte]": date_range[1],
}
# Build filter parameters
if provider_type:
params["filter[provider_type__in]"] = provider_type
if provider_alias:
params["filter[provider_alias__icontains]"] = provider_alias
if provider_uid:
params["filter[provider_uid__icontains]"] = provider_uid
if region:
params["filter[region__in]"] = region
if service:
params["filter[service__in]"] = service
if resource_type:
params["filter[type__in]"] = resource_type
if resource_name:
params["filter[name__icontains]"] = resource_name
if tag_key:
params["filter[tag_key]"] = tag_key
if tag_value:
params["filter[tag_value]"] = tag_value
if search:
params["filter[search]"] = search
# Pagination
params["page[size]"] = page_size
params["page[number]"] = page_number
# Return only LLM-relevant fields
params["fields[resources]"] = (
"uid,name,region,service,type,failed_findings_count,tags"
)
params["sort"] = "service,region,name"
# Convert lists to comma-separated strings
clean_params = self.api_client.build_filter_params(params)
# Get API response and transform to simplified format
api_response = await self.api_client.get(endpoint, params=clean_params)
simplified_response = ResourcesListResponse.from_api_response(api_response)
return simplified_response.model_dump()
async def get_resource(
self,
resource_id: str = Field(
description="Prowler's internal UUID (v4) for the resource to retrieve, generated when the resource was discovered in the system. Use `prowler_list_resources` tool to find the right ID"
),
) -> dict[str, Any]:
"""Retrieve comprehensive details about a specific resource by its ID.
IMPORTANT: This tool provides COMPLETE resource details with all available information.
Use this after finding a specific resource via prowler_list_resources.
This tool provides ALL information that prowler_list_resources returns PLUS:
1. Configuration Details:
- metadata: Provider-specific configuration (tags, policies, encryption settings, network rules)
- partition: Provider-specific partition/region grouping (e.g., aws, aws-cn, aws-eusc, aws-us-gov for AWS)
2. Temporal Tracking:
- inserted_at: When Prowler first discovered this resource
- updated_at: When resource configuration last changed
3. Security Relationships:
- finding_ids: Prowler's internal UUIDs (v4) of all security findings associated with this resource
- Use prowler_get_finding_details on these IDs to get remediation guidance
Useful Workflow:
1. Use prowler_list_resources to browse and filter across many resources
2. Use this tool to drill down into specific resources of interest
3. Use prowler_get_finding_details to get details about the security issues for specific resources
"""
params = {}
# Get API response and transform to detailed format
api_response = await self.api_client.get(
f"/resources/{resource_id}", params=params
)
detailed_resource = DetailedResource.from_api_response(
api_response.get("data", {})
)
return detailed_resource.model_dump()
async def get_resources_overview(
self,
provider_type: list[str] = Field(
default=[],
description="Filter by provider type. Multiple values allowed. If empty, all providers are returned. For valid values, please refer to Prowler Hub/Prowler Documentation that you can also find in form of tools in this MCP Server.",
),
provider_alias: str | None = Field(
default=None,
description="Filter by specific provider alias/name (partial match supported).",
),
provider_uid: str | None = Field(
default=None,
description="Filter by provider's native ID (e.g., AWS account ID, Azure subscription ID).",
),
date_from: str | None = Field(
default=None,
description="Start date for range query in ISO 8601 format (YYYY-MM-DD). Maximum 2-day range.",
),
date_to: str | None = Field(
default=None,
description="End date for range query in ISO 8601 format (YYYY-MM-DD).",
),
) -> dict[str, Any]:
"""Generate a markdown overview of your resources with statistics and insights.
IMPORTANT: This tool provides HIGH-LEVEL STATISTICS without returning individual resources.
Use this when you need a summary view before drilling into details.
The report includes:
- Total number of resources
- Available services across your providers
- Regions where resources are deployed
- Resource types present in your providers
Output format: Markdown-formatted report ready to present to users or include in documentation.
Use cases:
- Understanding infrastructure footprint
- Identifying resource concentration (which regions, services)
- Multi-provider deployment auditing
- Resource inventory reporting
- Tags planning (by provider, service, region)
"""
# Determine endpoint based on date parameters
date_range = self.api_client.normalize_date_range(
date_from, date_to, max_days=2
)
if date_range is None:
# No dates provided - use latest metadata endpoint
metadata_endpoint = "/resources/metadata/latest"
list_endpoint = "/resources/latest"
params = {}
else:
# Dates provided - use historical endpoints
metadata_endpoint = "/resources/metadata"
list_endpoint = "/resources"
params = {
"filter[updated_at__gte]": date_range[0],
"filter[updated_at__lte]": date_range[1],
}
# Build common filter parameters
if provider_type:
params["filter[provider_type__in]"] = provider_type
if provider_alias:
params["filter[provider_alias__icontains]"] = provider_alias
if provider_uid:
params["filter[provider_uid__icontains]"] = provider_uid
# Convert lists to comma-separated strings
clean_params = self.api_client.build_filter_params(params)
# Get metadata (services, regions, types)
metadata_params = clean_params.copy()
metadata_params["fields[resources-metadata]"] = "services,regions,types"
metadata_response = await self.api_client.get(
metadata_endpoint, params=metadata_params
)
metadata = ResourcesMetadataResponse.from_api_response(metadata_response)
# Get total count (using page_size=1 for efficiency)
count_params = clean_params.copy()
count_params["page[size]"] = 1
count_params["page[number]"] = 1
count_response = await self.api_client.get(list_endpoint, params=count_params)
total_resources = (
count_response.get("meta", {}).get("pagination", {}).get("count", 0)
)
# Build markdown report
report_lines = ["# Cloud Resources Overview", ""]
# Total resources
report_lines.append(f"**Total Resources**: {total_resources:,} resources")
report_lines.append("")
# Services
if metadata.services:
report_lines.append("## Services")
report_lines.append(f"**{len(metadata.services)}** unique services found")
report_lines.append("")
for i, service in enumerate(metadata.services, 1):
report_lines.append(f"{i}. {service}")
report_lines.append("")
# Regions
if metadata.regions:
report_lines.append("## Regions")
report_lines.append(f"**{len(metadata.regions)}** unique regions found")
report_lines.append("")
for i, region in enumerate(metadata.regions, 1):
report_lines.append(f"{i}. {region}")
report_lines.append("")
# Resource types
if metadata.types:
report_lines.append("## Resource Types")
report_lines.append(
f"**{len(metadata.types)}** unique resource types found"
)
report_lines.append("")
for i, rtype in enumerate(metadata.types, 1):
report_lines.append(f"{i}. {rtype}")
report_lines.append("")
report = "\n".join(report_lines)
return {"report": report}
async def get_resource_events(
self,
resource_id: str = Field(
description="Prowler's internal UUID (v4) for the resource. Use `prowler_list_resources` to find the right ID, or get it from a finding's resource relationship via `prowler_get_finding_details`."
),
lookback_days: int = Field(
default=90,
ge=1,
le=90,
description="How many days back to search for events. Range: 1-90. Default: 90.",
),
page_size: int = Field(
default=50,
ge=1,
le=50,
description="Number of events to return. Range: 1-50. Default: 50.",
),
include_read_events: bool = Field(
default=False,
description="Include read-only API calls (e.g., Describe*, Get*, List*). Default: false (write/modify events only).",
),
) -> dict[str, Any]:
"""Get the timeline of cloud API actions performed on a specific resource.
IMPORTANT: Currently only available for AWS resources. Uses CloudTrail to retrieve
the modification history of a resource, showing who did what and when.
Each event includes:
- What happened: event_name (e.g., PutBucketPolicy), event_source (e.g., s3.amazonaws.com)
- Who did it: actor, actor_type, actor_uid
- From where: source_ip_address, user_agent
- What changed: request_data, response_data (full API payloads)
- Errors: error_code, error_message (if the action failed)
Use cases:
- Investigating security incidents (who modified this resource?)
- Change tracking and audit trails
- Understanding resource configuration drift
- Identifying unauthorized or unexpected modifications
Workflows:
1. Resource browsing: prowler_list_resources → find resource → this tool for event history
2. Incident investigation: prowler_get_finding_details → get resource ID from finding → this tool to identify who caused the issue, what they changed, and when
"""
params = {
"lookback_days": lookback_days,
"page[size]": page_size,
"include_read_events": include_read_events,
}
clean_params = self.api_client.build_filter_params(params)
api_response = await self.api_client.get(
f"/resources/{resource_id}/events", params=clean_params
)
events_response = ResourceEventsResponse.from_api_response(api_response)
return events_response.model_dump()