feat: Shield 전체 기능 추가 — Rate Limiting CRUD, WAF custom rules, Metrics, DDoS enums, WAF profiles
All checks were successful
CI / build (push) Successful in 46s
All checks were successful
CI / build (push) Successful in 46s
This commit is contained in:
@@ -1,7 +1,15 @@
|
||||
"""Bunny Shield tools — Shield Zone API (/shield/shield-zone, /shield/shield-zones).
|
||||
"""Bunny Shield tools - Shield Zone API.
|
||||
|
||||
NOTE: Shield는 Pull Zone 필드(ShieldDDosProtectionEnabled 등)가 아니라
|
||||
별도 Shield Zone API로 관리됨. Pull Zone 업데이트로는 Shield를 켤 수 없음.
|
||||
API structure:
|
||||
- Shield Zone: /shield/shield-zone, /shield/shield-zones
|
||||
- WAF: /shield/waf/{shieldZoneId}, /shield/waf/custom-rules/{shieldZoneId}
|
||||
- Rate Limiting: /shield/rate-limits/{shieldZoneId}
|
||||
- Event Logs: /shield/event-logs/{shieldZoneId}
|
||||
- Metrics: /shield/metrics/overview/{shieldZoneId}
|
||||
- DDoS Enums: /shield/ddos/enums
|
||||
- WAF Profiles: /shield/waf/profiles
|
||||
|
||||
NOTE: Pull Zone fields (ShieldDDosProtectionEnabled) are read-only legacy. Do not use.
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -15,6 +23,8 @@ from ..config import logger
|
||||
|
||||
def register_shield_tools(mcp):
|
||||
|
||||
# -- Shield Zone CRUD --
|
||||
|
||||
@mcp.tool()
|
||||
async def bunny_shield_list() -> str:
|
||||
"""List all Shield Zones with WAF/DDoS configuration."""
|
||||
@@ -86,11 +96,13 @@ def register_shield_tools(mcp):
|
||||
logger.error("bunny_shield_update failed: %s", e)
|
||||
return f"Error: {e}"
|
||||
|
||||
# -- WAF --
|
||||
|
||||
@mcp.tool()
|
||||
async def bunny_waf_rules(
|
||||
shield_zone_id: Annotated[int, Field(description="Shield Zone ID")],
|
||||
) -> str:
|
||||
"""List WAF rules for a Shield Zone."""
|
||||
"""List all managed WAF rules for a Shield Zone."""
|
||||
try:
|
||||
data = await client.get(f"/shield/waf/{shield_zone_id}")
|
||||
return json.dumps(data, indent=2)
|
||||
@@ -98,6 +110,111 @@ def register_shield_tools(mcp):
|
||||
logger.error("bunny_waf_rules failed: %s", e)
|
||||
return f"Error: {e}"
|
||||
|
||||
@mcp.tool()
|
||||
async def bunny_waf_custom_rules(
|
||||
shield_zone_id: Annotated[int, Field(description="Shield Zone ID")],
|
||||
) -> str:
|
||||
"""List custom WAF rules for a Shield Zone."""
|
||||
try:
|
||||
data = await client.get(f"/shield/waf/custom-rules/{shield_zone_id}")
|
||||
return json.dumps(data, indent=2)
|
||||
except Exception as e:
|
||||
logger.error("bunny_waf_custom_rules failed: %s", e)
|
||||
return f"Error: {e}"
|
||||
|
||||
@mcp.tool()
|
||||
async def bunny_waf_custom_rule_create(
|
||||
shield_zone_id: Annotated[int, Field(description="Shield Zone ID")],
|
||||
rule: Annotated[str, Field(description="JSON string of custom WAF rule definition")],
|
||||
) -> str:
|
||||
"""Create a custom WAF rule for a Shield Zone."""
|
||||
try:
|
||||
parsed = json.loads(rule)
|
||||
parsed["shieldZoneId"] = shield_zone_id
|
||||
except json.JSONDecodeError as e:
|
||||
return f"Error: Invalid JSON: {e}"
|
||||
try:
|
||||
data = await client.post(f"/shield/waf/custom-rules/{shield_zone_id}", json=parsed)
|
||||
return json.dumps(data, indent=2)
|
||||
except Exception as e:
|
||||
logger.error("bunny_waf_custom_rule_create failed: %s", e)
|
||||
return f"Error: {e}"
|
||||
|
||||
@mcp.tool()
|
||||
async def bunny_waf_profiles() -> str:
|
||||
"""List available WAF profiles."""
|
||||
try:
|
||||
data = await client.get("/shield/waf/profiles")
|
||||
return json.dumps(data, indent=2)
|
||||
except Exception as e:
|
||||
logger.error("bunny_waf_profiles failed: %s", e)
|
||||
return f"Error: {e}"
|
||||
|
||||
# -- Rate Limiting --
|
||||
|
||||
@mcp.tool()
|
||||
async def bunny_rate_limits(
|
||||
shield_zone_id: Annotated[int, Field(description="Shield Zone ID")],
|
||||
) -> str:
|
||||
"""List rate limit rules for a Shield Zone."""
|
||||
try:
|
||||
data = await client.get(f"/shield/rate-limits/{shield_zone_id}")
|
||||
return json.dumps(data, indent=2)
|
||||
except Exception as e:
|
||||
logger.error("bunny_rate_limits failed: %s", e)
|
||||
return f"Error: {e}"
|
||||
|
||||
@mcp.tool()
|
||||
async def bunny_rate_limit_create(
|
||||
shield_zone_id: Annotated[int, Field(description="Shield Zone ID")],
|
||||
rule: Annotated[str, Field(description="JSON string of rate limit rule")],
|
||||
) -> str:
|
||||
"""Create a rate limit rule for a Shield Zone."""
|
||||
try:
|
||||
parsed = json.loads(rule)
|
||||
parsed["shieldZoneId"] = shield_zone_id
|
||||
except json.JSONDecodeError as e:
|
||||
return f"Error: Invalid JSON: {e}"
|
||||
try:
|
||||
data = await client.post(f"/shield/rate-limits/{shield_zone_id}", json=parsed)
|
||||
return json.dumps(data, indent=2)
|
||||
except Exception as e:
|
||||
logger.error("bunny_rate_limit_create failed: %s", e)
|
||||
return f"Error: {e}"
|
||||
|
||||
@mcp.tool()
|
||||
async def bunny_rate_limit_update(
|
||||
shield_zone_id: Annotated[int, Field(description="Shield Zone ID")],
|
||||
rule_id: Annotated[int, Field(description="Rate limit rule ID")],
|
||||
rule: Annotated[str, Field(description="JSON string of updated rule")],
|
||||
) -> str:
|
||||
"""Update a rate limit rule."""
|
||||
try:
|
||||
parsed = json.loads(rule)
|
||||
except json.JSONDecodeError as e:
|
||||
return f"Error: Invalid JSON: {e}"
|
||||
try:
|
||||
data = await client.put(f"/shield/rate-limits/{shield_zone_id}/{rule_id}", json=parsed)
|
||||
return json.dumps(data, indent=2) if isinstance(data, dict) else f"Rate limit {rule_id} updated"
|
||||
except Exception as e:
|
||||
logger.error("bunny_rate_limit_update failed: %s", e)
|
||||
return f"Error: {e}"
|
||||
|
||||
@mcp.tool()
|
||||
async def bunny_rate_limit_delete(
|
||||
shield_zone_id: Annotated[int, Field(description="Shield Zone ID")],
|
||||
rule_id: Annotated[int, Field(description="Rate limit rule ID to delete")],
|
||||
) -> str:
|
||||
"""Delete a rate limit rule."""
|
||||
try:
|
||||
await client.delete(f"/shield/rate-limits/{shield_zone_id}/{rule_id}")
|
||||
return f"Rate limit rule {rule_id} deleted"
|
||||
except Exception as e:
|
||||
logger.error("bunny_rate_limit_delete failed: %s", e)
|
||||
return f"Error: {e}"
|
||||
|
||||
# -- Event Logs --
|
||||
|
||||
@mcp.tool()
|
||||
async def bunny_waf_logs(
|
||||
shield_zone_id: Annotated[int, Field(description="Shield Zone ID")],
|
||||
@@ -112,3 +229,43 @@ def register_shield_tools(mcp):
|
||||
except Exception as e:
|
||||
logger.error("bunny_waf_logs failed: %s", e)
|
||||
return f"Error: {e}"
|
||||
|
||||
# -- Metrics --
|
||||
|
||||
@mcp.tool()
|
||||
async def bunny_shield_metrics(
|
||||
shield_zone_id: Annotated[int, Field(description="Shield Zone ID")],
|
||||
) -> str:
|
||||
"""Get Shield metrics overview: WAF, DDoS, Rate Limit, Bot Detection, Access List (28-day summary)."""
|
||||
try:
|
||||
data = await client.get(f"/shield/metrics/overview/{shield_zone_id}")
|
||||
if isinstance(data, dict) and "data" in data:
|
||||
d = data["data"]
|
||||
summary = {
|
||||
"overview": d.get("overview"),
|
||||
"waf": d.get("waf"),
|
||||
"dDoS": d.get("dDoS"),
|
||||
"ratelimit": d.get("ratelimit"),
|
||||
"botDetection": d.get("botDetection"),
|
||||
"accessList": d.get("accessList"),
|
||||
"uploadScanning": d.get("uploadScanning"),
|
||||
"totalBillableRequests": d.get("totalBillableRequests"),
|
||||
"totalCleanRequestsLimit": d.get("totalCleanRequestsLimit"),
|
||||
}
|
||||
return json.dumps(summary, indent=2)
|
||||
return json.dumps(data, indent=2)
|
||||
except Exception as e:
|
||||
logger.error("bunny_shield_metrics failed: %s", e)
|
||||
return f"Error: {e}"
|
||||
|
||||
# -- DDoS Enums --
|
||||
|
||||
@mcp.tool()
|
||||
async def bunny_ddos_enums() -> str:
|
||||
"""List DDoS sensitivity and execution mode enum values."""
|
||||
try:
|
||||
data = await client.get("/shield/ddos/enums")
|
||||
return json.dumps(data, indent=2)
|
||||
except Exception as e:
|
||||
logger.error("bunny_ddos_enums failed: %s", e)
|
||||
return f"Error: {e}"
|
||||
|
||||
Reference in New Issue
Block a user