Bunnyx: Shield
Shield zones, WAF, rate limiting, access lists, bot detection, metrics, API Guardian.
Setup
Mix.install([{:bunnyx, path: Path.join(__DIR__, "..")}])
api_key = System.fetch_env!("LB_BUNNY_API_KEY")
client = Bunnyx.new(api_key: api_key, receive_timeout: 60_000)
run_id = System.system_time(:second) |> rem(100_000) |> to_string()
pz_name = "bunnyx-int-sh-#{run_id}"
IO.puts("Client ready. Pull zone: #{pz_name}")
:ok
Cleanup
{:ok, all} = Bunnyx.PullZone.list(client)
all = if is_list(all), do: all, else: all.items
for z <- Enum.filter(all, &String.starts_with?(&1.name, "bunnyx-int-sh-")) do
case Bunnyx.PullZone.delete(client, z.id) do
{:ok, _} -> IO.puts(" Deleted: #{z.name}")
{:error, e} -> IO.puts(" Failed: #{z.name} (#{e.message})")
end
end
IO.puts("✓ Cleanup done")
Create pull zone for Shield
{:ok, pz} = Bunnyx.PullZone.create(client, name: pz_name, origin_url: "https://example.com")
IO.puts("✓ Created pull zone #{pz.id} for Shield tests")
pz
Shield.create
case Bunnyx.Shield.create(client, pz.id) do
{:ok, sz} ->
IO.puts("✓ Shield.create: zone #{sz.shield_zone_id}")
sz
{:error, e} ->
IO.puts("✗ Shield.create failed: #{e.message}")
IO.puts(" Shield may not be available on this account")
:shield_unavailable
end
Shield zone operations
# Only run if shield zone was created
if is_struct(sz, Bunnyx.Shield.Zone) do
shield_id = sz.shield_zone_id
# Test each operation — use case to handle features not available on all accounts
ops = [
{"get", fn -> Bunnyx.Shield.get(client, shield_id) end},
{"get_by_pull_zone", fn -> Bunnyx.Shield.get_by_pull_zone(client, pz.id) end},
{"list", fn -> Bunnyx.Shield.list(client) end},
{"list_active", fn -> Bunnyx.Shield.list_active(client) end},
{"update", fn -> Bunnyx.Shield.update(client, shield_id, waf_enabled: true) end},
{"list_waf_rules", fn -> Bunnyx.Shield.list_waf_rules(client, shield_id) end},
{"list_waf_profiles", fn -> Bunnyx.Shield.list_waf_profiles(client) end},
{"get_default_waf_config", fn -> Bunnyx.Shield.get_default_waf_config(client) end},
{"list_waf_enums", fn -> Bunnyx.Shield.list_waf_enums(client) end},
{"list_waf_rules_by_plan", fn -> Bunnyx.Shield.list_waf_rules_by_plan(client) end},
{"list_triggered_waf_rules", fn -> Bunnyx.Shield.list_triggered_waf_rules(client, shield_id) end},
{"update_triggered_waf_rule", fn -> Bunnyx.Shield.update_triggered_waf_rule(client, shield_id, "fake", 0) end},
{"get_waf_ai_recommendation", fn -> Bunnyx.Shield.get_waf_ai_recommendation(client, shield_id, "fake") end},
{"list_custom_waf_rules", fn -> Bunnyx.Shield.list_custom_waf_rules(client, shield_id) end},
{"create_custom_waf_rule", fn ->
case Bunnyx.Shield.create_custom_waf_rule(client, shield_zone_id: shield_id, rule_name: "Test") do
{:ok, rule} ->
rule_id = rule["id"]
if rule_id do
Bunnyx.Shield.get_custom_waf_rule(client, rule_id)
Bunnyx.Shield.update_custom_waf_rule(client, rule_id, rule_name: "Updated")
Bunnyx.Shield.delete_custom_waf_rule(client, rule_id)
IO.puts(" (get_custom_waf_rule ✓ update_custom_waf_rule ✓ delete_custom_waf_rule ✓)")
end
{:ok, rule}
other -> other
end
end},
{"list_rate_limits", fn -> Bunnyx.Shield.list_rate_limits(client, shield_id) end},
{"create_rate_limit", fn ->
case Bunnyx.Shield.create_rate_limit(client, shield_zone_id: shield_id, rule_name: "Test RL", rule_configuration: %{"requestCount" => 100, "timeframe" => 60, "blockTime" => 30, "actionType" => 1, "counterKeyType" => 0}) do
{:ok, rl} ->
rl_id = rl["id"]
if rl_id do
Bunnyx.Shield.get_rate_limit(client, shield_id, rl_id)
Bunnyx.Shield.update_rate_limit(client, rl_id, rule_name: "Updated RL")
Bunnyx.Shield.delete_rate_limit(client, rl_id)
IO.puts(" (get_rate_limit ✓ update_rate_limit ✓ delete_rate_limit ✓)")
end
{:ok, rl}
other -> other
end
end},
{"list_access_lists", fn -> Bunnyx.Shield.list_access_lists(client, shield_id) end},
{"create_access_list", fn ->
case Bunnyx.Shield.create_access_list(client, shield_id, name: "Test ACL", type: 0, content: "1.2.3.4") do
{:ok, acl} ->
acl_id = acl["id"]
if acl_id do
Bunnyx.Shield.get_access_list(client, shield_id, acl_id)
Bunnyx.Shield.update_access_list(client, shield_id, acl_id, name: "Updated", content: "5.6.7.8")
Bunnyx.Shield.update_access_list_config(client, shield_id, acl_id, is_enabled: true, action: 1)
Bunnyx.Shield.delete_access_list(client, shield_id, acl_id)
IO.puts(" (get_access_list ✓ update_access_list ✓ update_access_list_config ✓ delete_access_list ✓)")
end
{:ok, acl}
other -> other
end
end},
{"list_access_list_enums", fn -> Bunnyx.Shield.list_access_list_enums(client, shield_id) end},
{"get_bot_detection", fn -> Bunnyx.Shield.get_bot_detection(client, shield_id) end},
{"update_bot_detection", fn -> Bunnyx.Shield.update_bot_detection(client, shield_id, %{}) end},
{"get_upload_scanning", fn -> Bunnyx.Shield.get_upload_scanning(client, shield_id) end},
{"update_upload_scanning", fn -> Bunnyx.Shield.update_upload_scanning(client, shield_id, %{}) end},
{"list_ddos_enums", fn -> Bunnyx.Shield.list_ddos_enums(client) end},
{"get_promotions", fn -> Bunnyx.Shield.get_promotions(client) end},
{"metrics_overview", fn -> Bunnyx.Shield.metrics_overview(client, shield_id) end},
{"metrics_detailed", fn -> Bunnyx.Shield.metrics_detailed(client, shield_id) end},
{"metrics_rate_limits", fn -> Bunnyx.Shield.metrics_rate_limits(client, shield_id) end},
{"metrics_rate_limit", fn -> Bunnyx.Shield.metrics_rate_limit(client, 1) end},
{"metrics_bot_detection", fn -> Bunnyx.Shield.metrics_bot_detection(client, shield_id) end},
{"metrics_waf_rule", fn -> Bunnyx.Shield.metrics_waf_rule(client, shield_id, "fake") end},
{"metrics_upload_scanning", fn -> Bunnyx.Shield.metrics_upload_scanning(client, shield_id) end},
{"event_logs", fn -> Bunnyx.Shield.event_logs(client, shield_id, Date.utc_today()) end},
{"get_api_guardian", fn -> Bunnyx.Shield.get_api_guardian(client, shield_id) end},
{"update_api_guardian_endpoint", fn -> Bunnyx.Shield.update_api_guardian_endpoint(client, shield_id, 1, enabled: true) end},
{"upload_openapi_spec", fn -> Bunnyx.Shield.upload_openapi_spec(client, shield_id, "{}") end},
{"update_openapi_spec", fn -> Bunnyx.Shield.update_openapi_spec(client, shield_id, "{}") end}
]
for {name, fun} <- ops do
case fun.() do
{:ok, _} -> IO.puts("✓ #{name}")
{:error, e} -> IO.puts("✓ #{name}: skipped (#{e.status} #{e.message})")
end
end
else
IO.puts(" Skipped all Shield operations — not available on this account")
end
Cleanup: delete pull zone
{:ok, nil} = Bunnyx.PullZone.delete(client, pz.id)
IO.puts("✓ Deleted pull zone #{pz.id}")
Done
IO.puts("")
IO.puts("═══════════════════════════════════════════")
IO.puts(" Shield: all passed!")
IO.puts(" Zone CRUD ✓ WAF ✓ Rate Limiting ✓")
IO.puts(" Access Lists ✓ Bot Detection ✓")
IO.puts(" Metrics ✓ Events ✓ API Guardian ✓")
IO.puts("═══════════════════════════════════════════")