inqaba-security/gatekeeper
Composer 安装命令:
composer require inqaba-security/gatekeeper
包简介
Bridge Laravel applications to your SOC: normalized security event logging (ECS) to Microsoft Sentinel, Wazuh and syslog, with honeypots, endpoint-abuse detection and auth monitoring out of the box.
README 文档
README
Bridge your Laravel application to your SOC. Gatekeeper (inqaba-security/gatekeeper) turns application-level security signals — logins, registrations, honeypot hits, scanner probes, endpoint abuse — into normalized Elastic Common Schema (ECS) events and ships them to Microsoft Sentinel, Wazuh, or any syslog/CEF pipeline, without slowing your requests down.
Laravel app ──> SecurityEvent (ECS + MITRE ATT&CK) ──> queue ──> Sentinel / Wazuh / syslog / log
▲
├── auth events (login, register, lockout, ...)
├── honeypot routes (incl. parameterized decoys) & form honeypots
├── endpoint abuse detection (brute force, scanning, 429 hammering)
├── resource enumeration / IDOR probing detection (photos/1, 2, 3 ...)
├── request threat scanner (SQLi / XSS / traversal / JNDI probes)
└── Siem::log(...) custom events
+ GeoIP enrichment ──> Slack / Teams alerting
Features
- Normalized events — every event is an ECS 8.x document (
source.ip,user.id,event.action,event.outcome, ...) with MITRE ATT&CK tactic/technique tags, so analysts get consistent fields regardless of destination. - Microsoft Sentinel sink — Azure Monitor Logs Ingestion API (DCE/DCR, Entra ID auth) or the legacy HTTP Data Collector API.
- Wazuh sink — JSON-lines file tailed by the Wazuh agent (Wazuh's recommended pattern for custom app logs).
- Syslog sink — RFC 5424 over UDP/TCP with JSON or CEF payloads (works with Wazuh's syslog listener, rsyslog, or Sentinel's AMA/CEF connector).
- Auth monitoring — automatic listeners for Laravel's
Login,Failed,Logout,Registered,PasswordReset,Lockout,Verifiedevents; each individually toggleable. - Slack & Teams alerting — webhook sinks (Slack Block Kit / Teams Adaptive Card via Workflows) with a per-sink severity floor, so only high/critical events page the channel while the full stream flows to the SIEM.
- GeoIP enrichment — ECS
source.geo.*(country, city, coordinates, AS org) added at delivery time; local MaxMind database or HTTP driver, cached per IP, always off the request path. - Route honeypots — decoy endpoints (
/wp-login.php,/.env, ...) that log high-confidence attack traffic and can auto-block the source IP. Parameterized decoys (internal/photos/{id}) capture exactly which IDs were probed, with optional method and regex constraints. - Resource enumeration / IDOR detection — flags per-IP behaviour real users don't exhibit on parameterized routes: constant-stride ID scans (
photos/1, 2, 3...), abnormal distinct-ID breadth, and high 404/403 miss ratios from ID guessing. - Form honeypot — invisible field + minimum-fill-time check via a Blade component; reject or silently accept bot submissions.
- Endpoint abuse detection — per-IP thresholds for auth failures (brute force), 404 bursts (forced browsing / scanning), 429 hammering, and raw volume.
- Request threat scanner — passive heuristics for SQLi, XSS, path traversal, command/template injection, JNDI (log4shell-style) and PHP wrapper probes. Telemetry-first; optional blocking.
- IP blocklist — cache-backed, TTL'd, CIDR-aware exemptions, enforced by middleware, fed by honeypots/abuse detection or
Siem::blockIp(). - Queue shipping — events ship on a queue with retries + backoff; failures fall back to the local log so nothing is lost silently.
- Privacy controls — key-based redaction (
password,token, ...) and optional sha256 pseudonymization of usernames.
Installation
composer require inqaba-security/gatekeeper php artisan vendor:publish --tag=siem-config
Choose your destinations in .env:
SIEM_SINKS=sentinel,wazuh # any of: sentinel, wazuh, syslog, log, null SIEM_QUEUE=true # ship on the "siem" queue (recommended)
Verify connectivity end-to-end:
php artisan siem:test # all active sinks
php artisan siem:test --sink=sentinel
Sinks
Microsoft Sentinel (recommended: Logs Ingestion API)
- Create a Data Collection Endpoint (DCE) and a custom table (e.g.
Gatekeeper_CL) with a Data Collection Rule (DCR) in your Log Analytics workspace. - Create an Entra ID app registration and grant it the Monitoring Metrics Publisher role on the DCR.
- Configure:
SIEM_SENTINEL_MODE=dcr SIEM_SENTINEL_TENANT_ID=... SIEM_SENTINEL_CLIENT_ID=... SIEM_SENTINEL_CLIENT_SECRET=... SIEM_SENTINEL_DCE=https://<dce-name>.<region>.ingest.monitor.azure.com SIEM_SENTINEL_DCR_ID=dcr-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx SIEM_SENTINEL_STREAM=Custom-Gatekeeper_CL
Events include a TimeGenerated field ready for the DCR transform. Example hunting query:
Gatekeeper_CL | where event_action == "honeypot_route_hit" or event_severity >= 7 | summarize hits = count() by source_ip = tostring(source_ip), event_action | order by hits desc
For legacy workspaces, set SIEM_SENTINEL_MODE=legacy with SIEM_SENTINEL_WORKSPACE_ID and SIEM_SENTINEL_SHARED_KEY (HTTP Data Collector API — deprecated by Microsoft but still common).
Wazuh
Events are appended as JSON lines to a file the Wazuh agent tails:
SIEM_WAZUH_PATH=/var/log/gatekeeper/events.json
Agent ossec.conf:
<localfile> <log_format>json</log_format> <location>/var/log/gatekeeper/events.json</location> </localfile>
Example manager rule (all ECS fields are available to the JSON decoder):
<group name="laravel,siem,"> <rule id="100100" level="10"> <decoded_as>json</decoded_as> <field name="event.module">gatekeeper</field> <field name="event.action">honeypot_route_hit</field> <description>Laravel honeypot hit from $(source.ip)</description> <mitre><id>T1595</id></mitre> </rule> <rule id="100101" level="12"> <decoded_as>json</decoded_as> <field name="event.module">gatekeeper</field> <field name="event.action">brute_force_detected</field> <description>Brute force against Laravel app from $(source.ip)</description> <mitre><id>T1110</id></mitre> </rule> </group>
Rotate the file with logrotate (copytruncate) so the agent keeps its file handle.
Syslog / CEF
SIEM_SINKS=syslog SIEM_SYSLOG_HOST=siem.internal SIEM_SYSLOG_PORT=514 SIEM_SYSLOG_PROTOCOL=udp # or tcp (RFC 6587 octet framing) SIEM_SYSLOG_FORMAT=json # or cef
Use cef for ArcSight-style pipelines, including Sentinel's AMA/CEF connector and Wazuh's <remote> syslog listener.
Slack & Microsoft Teams (alerting)
SIEM_SINKS=sentinel,slack,teams SIEM_SLACK_WEBHOOK=https://hooks.slack.com/services/T000/B000/xxx SIEM_SLACK_MIN_SEVERITY=high # only high/critical page the channel SIEM_TEAMS_WEBHOOK=https://...logic.azure.com/workflows/... # Teams Workflows webhook SIEM_TEAMS_MIN_SEVERITY=high
Slack receives a Block Kit card (severity color bar, source IP + geo, user, request, MITRE technique, context). Teams receives an Adaptive Card via a Workflows/Power Automate "post to a channel when a webhook request is received" flow — the replacement for the retired O365 connectors. The minimum_severity floor is what makes these alerting channels rather than a firehose.
GeoIP enrichment
SIEM_GEOIP=true SIEM_GEOIP_DRIVER=maxmind # or: ipapi SIEM_GEOIP_MMDB=/var/lib/geoip/GeoLite2-City.mmdb
maxmind— offline lookups against a local GeoLite2/GeoIP2 City database. Requirescomposer require geoip2/geoip2.ipapi— HTTP lookups (ip-api.com format by default, URL configurable). Zero dependencies; check your provider's terms.
Enrichment happens at delivery time on the queue worker, results (including misses) are cached per IP for 24h, and any resolver failure degrades to an un-enriched event — GeoIP can never delay a request or lose an event. Fields land under ECS source.geo.* / source.as.* and show up in Slack/Teams alerts as 203.0.113.9 (Oslo, NO).
Custom resolver: bind your own Inqaba\Gatekeeper\Geo\GeoIpResolver implementation in the container.
Middleware
Register where appropriate (typically the global stack or the web/api groups):
| Alias | Purpose |
|---|---|
siem.block |
Enforce the IP blocklist (put it first) |
siem.abuse |
Per-IP brute force / scanning / rate-abuse detection |
siem.scan |
Request payload threat scanner (detection-only by default) |
siem.enum |
Resource enumeration / IDOR probing detection on parameterized routes |
siem.honeypot |
Form honeypot validation on POST/PUT/PATCH/DELETE routes |
// bootstrap/app.php (Laravel 11+) ->withMiddleware(function (Middleware $middleware) { $middleware->prepend(\Inqaba\Gatekeeper\Http\Middleware\BlockDeniedIps::class); $middleware->web(append: [ \Inqaba\Gatekeeper\Http\Middleware\DetectEndpointAbuse::class, \Inqaba\Gatekeeper\Http\Middleware\ScanRequestThreats::class, ]); })
Honeypots
Route honeypots are registered automatically from config('siem.honeypots.routes.paths') — decoy paths like wp-login.php, .env, phpmyadmin. Any hit emits a high-severity honeypot_route_hit event and (by default) blocks the source IP for 24h. Add paths that make sense for your stack; remove any that collide with real routes.
Decoys can take route parameters — useful for trapping ID-guessing scripts on endpoints that look like real resources:
'paths' => [ 'wp-login.php', 'internal/photos/{id}', // params are captured in the event context ['path' => 'legacy/export/{file}', 'methods' => ['GET'], 'where' => ['file' => '.*\.sql']], ],
The probed parameter values (route_params) and the matched pattern land in the event, so the SOC sees exactly what the attacker tried to pull. Make sure a decoy never shadows a real route.
Form honeypot — drop the component into any form and protect the route:
<form method="POST" action="/register"> @csrf <x-siem::honeypot /> ... </form>
Route::post('/register', ...)->middleware('siem.honeypot');
Bots that fill the invisible field or submit faster than min_seconds are logged and rejected (respond => 'reject') or fed a fake success (respond => 'silent').
Resource enumeration / IDOR detection
Attach siem.enum to routes with parameters:
Route::get('/photos/{photo}', [PhotoController::class, 'show']) ->middleware('siem.enum');
Per IP and per route pattern (photos/{photo}), within a 5-minute window, it raises a high-severity resource_enumeration_detected event when it sees behaviour no human browsing session produces:
| Pattern | Trigger (defaults) | What it catches |
|---|---|---|
sequential_scan |
≥ 5 numeric IDs advancing with a constant stride | photos/1, 2, 3... and photos/10, 20, 30... scrapers |
high_distinct_ids |
≥ 15 distinct IDs touched | breadth-first scraping, incl. non-numeric IDs/slugs |
high_miss_ratio |
≥ 50% 404/403/410 across ≥ 10 requests | IDOR guessing — probing IDs that don't exist or aren't theirs |
Events include the resource pattern, distinct-ID count, detected stride, miss counts and a sample of probed IDs. Each pattern fires once per IP per window, and SIEM_ENUM_AUTOBLOCK=true blocks the scanner outright. Tune thresholds in config('siem.enumeration').
Custom events
use Inqaba\Gatekeeper\Facades\Siem; use Inqaba\Gatekeeper\Events\{EventType, Severity}; // One-liner Siem::log('export_download', 'Customer data export downloaded', Severity::High, [ 'rows' => 12000, ], $request->user()); // Fluent builder with a catalog type Siem::event(EventType::AccessDenied) ->user($request->user()) ->fromRequest($request) ->with(['ability' => 'orders.export']) ->report();
Custom sink drivers:
Siem::extend('kafka', fn ($app) => new KafkaSink(...)); // config/siem.php: 'active' => ['kafka'],
Blocklist API
Siem::blockIp('203.0.113.7', minutes: 120, reason: 'analyst-decision'); Siem::unblockIp('203.0.113.7'); Siem::blocklist()->isBlocked('203.0.113.7');
IPs/CIDRs in siem.blocklist.never_block (health checks, offices, load balancer probes) can never be auto-blocked.
Event catalog
event.action |
Severity | MITRE | Emitted by |
|---|---|---|---|
login_succeeded / login_failed / logout |
info / low / info | T1078 / T1110 / — | auth listener |
user_registered, password_reset*, email_verified |
info–low | — | auth listener |
auth_lockout |
high | T1110 | auth listener |
honeypot_route_hit |
high | T1595.003 | honeypot routes |
honeypot_form_triggered |
medium | — | siem.honeypot |
brute_force_detected |
critical | T1110 | siem.abuse |
scanning_detected |
high | T1595.003 | siem.abuse |
rate_limit_abuse / endpoint_abuse |
medium / high | T1595 | siem.abuse |
suspicious_input |
high | T1190 | siem.scan |
resource_enumeration_detected |
high | T1119 | siem.enum |
blocked_ip_attempt |
low | — | siem.block |
anything via Siem::log() |
your choice | — | your code |
Operational notes
- Run a queue worker for the
siemqueue (php artisan queue:work --queue=siem). WithSIEM_QUEUE=falseevents ship inline — fine for the file/log sinks, not for network sinks. - Failure handling: sink failures are retried (3 tries, backoff 5/30/120s) and then written to the local Laravel log with the full event, so security telemetry is never dropped silently.
- Noise control: abuse detections fire once per IP per window; blocked-IP events once per IP per 5 minutes;
SIEM_MIN_SEVERITYfilters globally. - Privacy: context keys like
password/tokenare always redacted; setSIEM_HASH_USERNAMES=trueto pseudonymize identifiers before they leave the app. - The scanner and abuse detectors are telemetry, not a WAF — keep your WAF/CDN protections in front; this package gives your SOC the application-layer signal those layers can't see.
Testing
composer install
composer test
License
MIT
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-14