mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-21 19:04:19 +00:00
487 lines
16 KiB
Python
487 lines
16 KiB
Python
"""
|
|
Prowler Hub MCP module
|
|
|
|
Provides access to Prowler Hub API for security checks and compliance frameworks.
|
|
"""
|
|
|
|
from typing import Any, Optional
|
|
|
|
import httpx
|
|
from fastmcp import FastMCP
|
|
from prowler_mcp_server import __version__
|
|
|
|
# Initialize FastMCP for Prowler Hub
|
|
hub_mcp_server = FastMCP("prowler-hub")
|
|
|
|
# API base URL
|
|
BASE_URL = "https://hub.prowler.com/api"
|
|
|
|
# HTTP client configuration
|
|
prowler_hub_client = httpx.Client(
|
|
base_url=BASE_URL,
|
|
timeout=30.0,
|
|
headers={
|
|
"Accept": "application/json",
|
|
"User-Agent": f"prowler-mcp-server/{__version__}",
|
|
},
|
|
)
|
|
|
|
# GitHub raw content base URL for Prowler checks
|
|
GITHUB_RAW_BASE = (
|
|
"https://raw.githubusercontent.com/prowler-cloud/prowler/refs/heads/master/"
|
|
"prowler/providers"
|
|
)
|
|
|
|
# Separate HTTP client for GitHub raw content
|
|
github_raw_client = httpx.Client(
|
|
timeout=30.0,
|
|
headers={
|
|
"Accept": "*/*",
|
|
"User-Agent": f"prowler-mcp-server/{__version__}",
|
|
},
|
|
)
|
|
|
|
|
|
def github_check_path(provider_id: str, check_id: str, suffix: str) -> str:
|
|
"""Build the GitHub raw URL for a given check artifact suffix using provider
|
|
and check_id.
|
|
|
|
Suffix examples: ".metadata.json", ".py", "_fixer.py"
|
|
"""
|
|
try:
|
|
service_id = check_id.split("_", 1)[0]
|
|
except IndexError:
|
|
service_id = check_id
|
|
return f"{GITHUB_RAW_BASE}/{provider_id}/services/{service_id}/{check_id}/{check_id}{suffix}"
|
|
|
|
|
|
@hub_mcp_server.tool()
|
|
async def get_check_filters() -> dict[str, Any]:
|
|
"""
|
|
Get available values for filtering for tool `get_checks`. Recommended to use before calling `get_checks` to get the available values for the filters.
|
|
|
|
Returns:
|
|
Available filter options including providers, types, services, severities,
|
|
categories, and compliance frameworks with their respective counts
|
|
"""
|
|
try:
|
|
response = prowler_hub_client.get("/check/filters")
|
|
response.raise_for_status()
|
|
filters = response.json()
|
|
|
|
return {"filters": filters}
|
|
except httpx.HTTPStatusError as e:
|
|
return {
|
|
"error": f"HTTP error {e.response.status_code}: {e.response.text}",
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
# Security Check Tools
|
|
@hub_mcp_server.tool()
|
|
async def get_checks(
|
|
providers: Optional[str] = None,
|
|
types: Optional[str] = None,
|
|
services: Optional[str] = None,
|
|
severities: Optional[str] = None,
|
|
categories: Optional[str] = None,
|
|
compliances: Optional[str] = None,
|
|
ids: Optional[str] = None,
|
|
fields: Optional[str] = "id,service,severity,title,description,risk",
|
|
) -> dict[str, Any]:
|
|
"""
|
|
List security Prowler Checks. The list can be filtered by the parameters defined for the tool.
|
|
It is recommended to use the tool `get_check_filters` to get the available values for the filters.
|
|
A not filtered request will return more than 1000 checks, so it is recommended to use the filters.
|
|
|
|
Args:
|
|
providers: Filter by Prowler provider IDs. Example: "aws,azure". Use the tool `list_providers` to get the available providers IDs.
|
|
types: Filter by check types.
|
|
services: Filter by provider services IDs. Example: "s3,keyvault". Use the tool `list_providers` to get the available services IDs in a provider.
|
|
severities: Filter by severity levels. Example: "medium,high". Available values are "low", "medium", "high", "critical".
|
|
categories: Filter by categories. Example: "cluster-security,encryption".
|
|
compliances: Filter by compliance framework IDs. Example: "cis_4.0_aws,ens_rd2022_azure".
|
|
ids: Filter by specific check IDs. Example: "s3_bucket_level_public_access_block".
|
|
fields: Specify which fields from checks metadata to return (id is always included). Example: "id,title,description,risk".
|
|
Available values are "id", "title", "description", "provider", "type", "service", "subservice", "severity", "risk", "reference", "remediation", "services_required", "aws_arn_template", "notes", "categories", "default_value", "resource_type", "related_url", "depends_on", "related_to", "fixer".
|
|
The default parameters are "id,title,description".
|
|
If null, all fields will be returned.
|
|
|
|
Returns:
|
|
List of security checks matching the filters. The structure is as follows:
|
|
{
|
|
"count": N,
|
|
"checks": [
|
|
{"id": "check_id_1", "title": "check_title_1", "description": "check_description_1", ...},
|
|
{"id": "check_id_2", "title": "check_title_2", "description": "check_description_2", ...},
|
|
{"id": "check_id_3", "title": "check_title_3", "description": "check_description_3", ...},
|
|
...
|
|
]
|
|
}
|
|
"""
|
|
params: dict[str, str] = {}
|
|
|
|
if providers:
|
|
params["providers"] = providers
|
|
if types:
|
|
params["types"] = types
|
|
if services:
|
|
params["services"] = services
|
|
if severities:
|
|
params["severities"] = severities
|
|
if categories:
|
|
params["categories"] = categories
|
|
if compliances:
|
|
params["compliances"] = compliances
|
|
if ids:
|
|
params["ids"] = ids
|
|
if fields:
|
|
params["fields"] = fields
|
|
|
|
try:
|
|
response = prowler_hub_client.get("/check", params=params)
|
|
response.raise_for_status()
|
|
checks = response.json()
|
|
|
|
checks_dict = {}
|
|
for check in checks:
|
|
check_data = {}
|
|
# Always include the id field as it's mandatory for the response structure
|
|
if "id" in check:
|
|
check_data["id"] = check["id"]
|
|
|
|
# Include other requested fields
|
|
for field in fields.split(","):
|
|
if field != "id" and field in check: # Skip id since it's already added
|
|
check_data[field] = check[field]
|
|
checks_dict[check["id"]] = check_data
|
|
|
|
return {"count": len(checks), "checks": checks_dict}
|
|
except httpx.HTTPStatusError as e:
|
|
return {
|
|
"error": f"HTTP error {e.response.status_code}: {e.response.text}",
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
@hub_mcp_server.tool()
|
|
async def get_check_raw_metadata(
|
|
provider_id: str,
|
|
check_id: str,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Fetch the raw check metadata JSON, this is a low level version of the tool `get_checks`.
|
|
It is recommended to use the tool `get_checks` filtering about the `ids` parameter instead of using this tool.
|
|
|
|
Args:
|
|
provider_id: Prowler provider ID (e.g., "aws", "azure").
|
|
check_id: Prowler check ID (folder and base filename).
|
|
|
|
Returns:
|
|
Raw metadata JSON as stored in Prowler.
|
|
"""
|
|
if provider_id and check_id:
|
|
url = github_check_path(provider_id, check_id, ".metadata.json")
|
|
try:
|
|
resp = github_raw_client.get(url)
|
|
resp.raise_for_status()
|
|
return resp.json()
|
|
except httpx.HTTPStatusError as e:
|
|
if e.response.status_code == 404:
|
|
return {
|
|
"error": f"Check {check_id} not found in Prowler",
|
|
}
|
|
else:
|
|
return {
|
|
"error": f"HTTP error {e.response.status_code}: {e.response.text}",
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"error": f"Error fetching check {check_id} from Prowler: {str(e)}",
|
|
}
|
|
else:
|
|
return {
|
|
"error": "Provider ID and check ID are required",
|
|
}
|
|
|
|
|
|
@hub_mcp_server.tool()
|
|
async def get_check_code(
|
|
provider_id: str,
|
|
check_id: str,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Fetch the check implementation Python code from Prowler.
|
|
|
|
Args:
|
|
provider_id: Prowler provider ID (e.g., "aws", "azure").
|
|
check_id: Prowler check ID (e.g., "opensearch_service_domains_not_publicly_accessible").
|
|
|
|
Returns:
|
|
Dict with the code content as text.
|
|
"""
|
|
if provider_id and check_id:
|
|
url = github_check_path(provider_id, check_id, ".py")
|
|
try:
|
|
resp = github_raw_client.get(url)
|
|
resp.raise_for_status()
|
|
return {
|
|
"content": resp.text,
|
|
}
|
|
except httpx.HTTPStatusError as e:
|
|
if e.response.status_code == 404:
|
|
return {
|
|
"error": f"Check {check_id} not found in Prowler",
|
|
}
|
|
else:
|
|
return {
|
|
"error": f"HTTP error {e.response.status_code}: {e.response.text}",
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"error": str(e),
|
|
}
|
|
else:
|
|
return {
|
|
"error": "Provider ID and check ID are required",
|
|
}
|
|
|
|
|
|
@hub_mcp_server.tool()
|
|
async def get_check_fixer(
|
|
provider_id: str,
|
|
check_id: str,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Fetch the check fixer Python code from Prowler, if it exists.
|
|
|
|
Args:
|
|
provider_id: Prowler provider ID (e.g., "aws", "azure").
|
|
check_id: Prowler check ID (e.g., "opensearch_service_domains_not_publicly_accessible").
|
|
|
|
Returns:
|
|
Dict with fixer content as text if present, existence flag.
|
|
"""
|
|
if provider_id and check_id:
|
|
url = github_check_path(provider_id, check_id, "_fixer.py")
|
|
try:
|
|
resp = github_raw_client.get(url)
|
|
if resp.status_code == 404:
|
|
return {
|
|
"error": f"Fixer not found for check {check_id}",
|
|
}
|
|
resp.raise_for_status()
|
|
return {
|
|
"content": resp.text,
|
|
}
|
|
except httpx.HTTPStatusError as e:
|
|
if e.response.status_code == 404:
|
|
return {
|
|
"error": f"Check {check_id} not found in Prowler",
|
|
}
|
|
else:
|
|
return {
|
|
"error": f"HTTP error {e.response.status_code}: {e.response.text}",
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"error": str(e),
|
|
}
|
|
else:
|
|
return {
|
|
"error": "Provider ID and check ID are required",
|
|
}
|
|
|
|
|
|
@hub_mcp_server.tool()
|
|
async def search_checks(term: str) -> dict[str, Any]:
|
|
"""
|
|
Search the term across all text properties of check metadata.
|
|
|
|
Args:
|
|
term: Search term to find in check titles, descriptions, and other text fields
|
|
|
|
Returns:
|
|
List of checks matching the search term
|
|
"""
|
|
try:
|
|
response = prowler_hub_client.get("/check/search", params={"term": term})
|
|
response.raise_for_status()
|
|
checks = response.json()
|
|
|
|
return {
|
|
"count": len(checks),
|
|
"checks": checks,
|
|
}
|
|
except httpx.HTTPStatusError as e:
|
|
return {
|
|
"error": f"HTTP error {e.response.status_code}: {e.response.text}",
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
# Compliance Framework Tools
|
|
@hub_mcp_server.tool()
|
|
async def get_compliance_frameworks(
|
|
provider: Optional[str] = None,
|
|
fields: Optional[
|
|
str
|
|
] = "id,framework,provider,description,total_checks,total_requirements",
|
|
) -> dict[str, Any]:
|
|
"""
|
|
List and filter compliance frameworks. The list can be filtered by the parameters defined for the tool.
|
|
|
|
Args:
|
|
provider: Filter by one Prowler provider ID. Example: "aws". Use the tool `list_providers` to get the available providers IDs.
|
|
fields: Specify which fields to return (id is always included). Example: "id,provider,description,version".
|
|
It is recommended to run with the default parameters because the full response is too large.
|
|
Available values are "id", "framework", "provider", "description", "total_checks", "total_requirements", "created_at", "updated_at".
|
|
The default parameters are "id,framework,provider,description,total_checks,total_requirements".
|
|
If null, all fields will be returned.
|
|
|
|
Returns:
|
|
List of compliance frameworks. The structure is as follows:
|
|
{
|
|
"count": N,
|
|
"frameworks": {
|
|
"framework_id": {
|
|
"id": "framework_id",
|
|
"provider": "provider_id",
|
|
"description": "framework_description",
|
|
"version": "framework_version"
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
params = {}
|
|
|
|
if provider:
|
|
params["provider"] = provider
|
|
if fields:
|
|
params["fields"] = fields
|
|
|
|
try:
|
|
response = prowler_hub_client.get("/compliance", params=params)
|
|
response.raise_for_status()
|
|
frameworks = response.json()
|
|
|
|
frameworks_dict = {}
|
|
for framework in frameworks:
|
|
framework_data = {}
|
|
# Always include the id field as it's mandatory for the response structure
|
|
if "id" in framework:
|
|
framework_data["id"] = framework["id"]
|
|
|
|
# Include other requested fields
|
|
for field in fields.split(","):
|
|
if (
|
|
field != "id" and field in framework
|
|
): # Skip id since it's already added
|
|
framework_data[field] = framework[field]
|
|
frameworks_dict[framework["id"]] = framework_data
|
|
|
|
return {"count": len(frameworks), "frameworks": frameworks_dict}
|
|
except httpx.HTTPStatusError as e:
|
|
return {
|
|
"error": f"HTTP error {e.response.status_code}: {e.response.text}",
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
@hub_mcp_server.tool()
|
|
async def search_compliance_frameworks(term: str) -> dict[str, Any]:
|
|
"""
|
|
Search compliance frameworks by term.
|
|
|
|
Args:
|
|
term: Search term to find in framework names and descriptions
|
|
|
|
Returns:
|
|
List of compliance frameworks matching the search term
|
|
"""
|
|
try:
|
|
response = prowler_hub_client.get("/compliance/search", params={"term": term})
|
|
response.raise_for_status()
|
|
frameworks = response.json()
|
|
|
|
return {
|
|
"count": len(frameworks),
|
|
"search_term": term,
|
|
"frameworks": frameworks,
|
|
}
|
|
except httpx.HTTPStatusError as e:
|
|
return {
|
|
"error": f"HTTP error {e.response.status_code}: {e.response.text}",
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
# Provider Tools
|
|
@hub_mcp_server.tool()
|
|
async def list_providers() -> dict[str, Any]:
|
|
"""
|
|
Get all available Prowler providers and their associated services.
|
|
|
|
Returns:
|
|
List of Prowler providers with their associated services. The structure is as follows:
|
|
{
|
|
"count": N,
|
|
"providers": {
|
|
"provider_id": {
|
|
"name": "provider_name",
|
|
"services": ["service_id_1", "service_id_2", "service_id_3", ...]
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
response = prowler_hub_client.get("/providers")
|
|
response.raise_for_status()
|
|
providers = response.json()
|
|
|
|
providers_dict = {}
|
|
for provider in providers:
|
|
providers_dict[provider["id"]] = {
|
|
"name": provider.get("name", ""),
|
|
"services": provider.get("services", []),
|
|
}
|
|
|
|
return {"count": len(providers), "providers": providers_dict}
|
|
except httpx.HTTPStatusError as e:
|
|
return {
|
|
"error": f"HTTP error {e.response.status_code}: {e.response.text}",
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
# Analytics Tools
|
|
@hub_mcp_server.tool()
|
|
async def get_artifacts_count() -> dict[str, Any]:
|
|
"""
|
|
Get total count of security artifacts (checks + compliance frameworks).
|
|
|
|
Returns:
|
|
Total number of artifacts in the Prowler Hub.
|
|
"""
|
|
try:
|
|
response = prowler_hub_client.get("/n_artifacts")
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
|
|
return {
|
|
"total_artifacts": data.get("n", 0),
|
|
"details": "Total count includes both security checks and compliance frameworks",
|
|
}
|
|
except httpx.HTTPStatusError as e:
|
|
return {
|
|
"error": f"HTTP error {e.response.status_code}: {e.response.text}",
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|