Overview — what it does
The Whitelist page manages IPs and ranges to exempt from InfraGuard's automatic blocking pipeline. Anything registered here becomes the "exemption roster" that the detection collectors consult immediately before they block — preventing trusted sources (CDNs, partner APIs, office links) from being blocked by a false positive.
The central concept is expiry:
expires_atis NULL → permanent exemption (never expires). For always-trusted sources such as CDNs and office links.expires_atis a date → temporary exemption (auto-lapses after that instant). For short investigation windows or auto-innocence verdicts.
Exemptions arrive from three sources:
| Source | Value | Meaning |
|---|---|---|
| Manual | manual |
Operator adds it directly from the UI |
| Auto (innocent) | auto |
srct_tx reputation-checks an IP, and if clearly innocent, auto-registers a 30-day exemption |
| Seed | seed |
Previously hardcoded CDN/OFFICE ranges migrated in by InfrWhitelistSeeder (permanent) |
The
infr_whitelisttable lives on thelegacy_gloryconnection (the same DB asblacklist_infoand thesuricata_*tables), not the app DB. That lets the Node collectorsrct_txread this table directly just before blocking and write innocent IPs directly into it.
When to use
- Prevent false positives: pre-register normal traffic (monitoring bots, partner crawlers, payment callbacks) that could trip scan/rate heuristics.
- Temporary exception during investigation: give a range an expiry date to exempt it for a few days.
- Always-trust CDN/office: permanently exempt front-end IPs of Cloudflare/Google/AWS/Akamai or a fixed office IP.
- Review auto verdicts: inspect, extend, or delete the
autoentriessrct_txcreated. - Un-block an IP that was blocked by mistake in the Blocklist and keep it from being re-blocked (register an exemption).
Layout
Top KPIs (5)
Computed by WhitelistService::summary() with a 30-second cache. Active = is_active = 1 AND (expires_at IS NULL OR expires_at > now).
| KPI | Meaning | Calculation |
|---|---|---|
| Active exemptions | Total currently-effective exemptions | count of active |
| Permanent | No-expiry entries | active AND expires_at IS NULL |
| Temporary | Entries that will expire | active − permanent |
| Auto exemptions | Innocent-verdict auto entries | active AND source = 'auto' |
| Expiring soon | Expiring within 7 days | active AND expires_at <= now+7d |
Exemption list
A filterable table (25 rows per page). Sort is permanent-first (expires_at IS NULL DESC) → expiry ascending → id descending.
| Column | Description |
|---|---|
| Range | Normalized cidr (e.g. 203.0.113.0/24 or 203.0.113.10/32) |
| Label | label (e.g. "Cloudflare", "Office") |
| Source | manual / auto / seed badge |
| Expiry | Permanent / YYYY-MM-DD HH:MM / n days left / expired |
| Added | created_at |
| Action | Delete (when you have write access) |
Filters: status (active · expired · all), source (manual · auto · seed · all), search (IP · CIDR · label). The list polls for updates every 30 seconds.
Add-manual modal
The [Add manually] button opens a modal. Inputs: IP or CIDR, label (optional), expiry datetime (optional; blank = permanent).
How to use
1. Add a manual exemption
- Click
[Add manually]. - Enter an IP or CIDR. A single IP is normalized to
/32; a range is normalized to its network address (see the normalization rules below). - Optionally enter a label and an expiry datetime. Leave expiry blank for a permanent exemption.
- Click
[Add]. If the samecidralready exists it is reactivated (is_active=1), and its expiry/label are updated.
2. Normalization (normalizeCidr) rules
WhitelistService::normalizeCidr() rewrites input to a canonical form (locale-independent, identical to the srct_tx matching convention).
203.0.113.10 → 203.0.113.10/32 (single IP becomes /32)
203.0.113.0/24 → 203.0.113.0/24 (unchanged)
203.0.113.55/24 → 203.0.113.0/24 (host bits masked → network address)
10.0.0.5/8 → 10.0.0.0/8
999.1.1.1 → (invalid → null, 422 invalid_ip)
- IPv4 only. Prefix length 0–32.
- Before saving, the controller pre-validates with the regex
^\d{1,3}\.\d{1,3}\.\d{1,3}(\.\d{1,3})?(/\d{1,2})?$, and the service finalizes withfilter_var(... FILTER_FLAG_IPV4)+ip2long.
When adding a
/24range you can leave host bits in —203.0.113.55/24is stored automatically as203.0.113.0/24(its network address).
3. Entering expiry
- Empty → permanent (
expires_at = NULL). YYYY-MM-DDorYYYY-MM-DD HH:MM→ parsed withCarbon::parse.- A parse failure also becomes permanent (
parseExpiry→ null).
4. Delete
The delete action on a list row calls remove after confirmation. The row is hard-deleted (not soft-deleted). Even a seed CDN entry can be deleted, but it may be recreated on the next seeder run.
5. srct_tx auto-innocence (auto source) — how it gets populated
The srct_tx collector verifies innocence with a very strict procedure immediately before blocking a scan/rate-anomaly IP, and only leaves an auto entry here when the verdict is certain.
- Whitelist filter — if already present in
infr_whitelist(+ in-code CDN list), exempt and skip immediately. - Reputation check for E-only candidates only — only IPs that carry the false-positive-prone "short scan alone" signal (heuristic E) with none of the hard signals (A/A2/B/C/D) are checked against AbuseIPDB (round-robin keys · 6h cache · per-sweep cap).
- Fully-innocent verdict — if
abuse_score=0ANDreports=0AND not Tor (or whitelisted + score 0), it auto-registers a 30-day exemption (ip/32) intoinfr_whitelist(source=auto)and skips the block.
Auto-innocence is
srct_tx-only.kts_tx(Kitsune ML) only reads and filters the whitelist — it does no reputation lookups and no auto-registration.
6. cidrList() — reused for code generation
WhitelistService::cidrList() returns the deduplicated active, unexpired CIDRs (30-second cache). That list is injected as the source for the CDN_WHITELIST_CIDRS constant when the Rules & Thresholds page generates the srct_tx/kts_tx scripts — so exemptions managed in the UI flow into the actual collector code.
Data sources
Table infr_whitelist (connection: legacy_glory, migration 000734)
| Column | Type | Description |
|---|---|---|
id |
bigint PK | |
cidr |
varchar(43) unique | normalized IP (/32) or network (/n) |
label |
varchar(120) null | what/who |
source |
varchar(12) | manual / auto / seed |
reason |
varchar(500) null | auto rationale (reputation score/category) |
country |
varchar(4) null | country code |
expires_at |
datetime null | NULL = permanent |
is_active |
tinyint(1) | active flag |
created_by |
int null | registrant user id |
created_at / updated_at |
timestamps |
Indexes: (is_active, expires_at), source.
infr_whitelistis created on thelegacy_glory(GLORY) DB. Iflegacy_gloryis not configured as mysql/mariadb, the migration and seeder are guarded to no-op — the page renders but KPIs read 0. In real deployments the GLORY connection must be filled in.
Seeder InfrWhitelistSeeder (source=seed, permanent)
Migrates the previously hardcoded CDN/OFFICE ranges (idempotent via firstOrCreate). Includes front-end ranges for Cloudflare, Google, Meta, AWS, Akamai plus an OFFICE fixed IP.
php artisan db:seed --class=InfrWhitelistSeeder
Model · Service
- Model:
App\Models\Infr\InfrWhitelist(protected $connection = 'legacy_glory'). - Service:
App\Services\Security\WhitelistService—summary()(KPI, 30s cache),page()(filter/paginate),add()/remove()(writes),cidrList()(code-gen source),normalizeCidr()(static SOT). - Controller:
App\Http\Controllers\Security\WhitelistController—index·live·add·remove.
Access
| Action | Minimum level | Notes |
|---|---|---|
| Enter/view Security area | Security menu min_req 2 |
demo(1) does not see the Security menu |
| Add/remove whitelist (write) | level ≥ 2 | WhitelistController::canWrite() = user_level >= 2 |
Level scheme: demo=1 · staff=5 · admin=10. So staff and admin can both read and write; a write failure returns 403 forbidden (toast "Requires level 2+").
Every write is recorded in InfrAuditLog:
- Add →
sec.whitelist_add(target=ip, meta hasid·reactivated). - Remove →
sec.whitelist_remove(target=ip, meta hasid).
See RBAC levels for the full level/role policy and Audit log for reviewing writes.
Troubleshooting & FAQ
All KPIs are 0 and the list is empty
The legacy_glory connection is not configured, or the infr_whitelist table does not exist yet. Check the GLORY DB settings and run the migration/seeder. The service returns empty values gracefully on DB errors.
"Not a valid IP/CIDR" (422)
The input is not IPv4 or the prefix length is outside 0–32. IPv6 is not supported. Examples: 203.0.113.10, 203.0.113.0/24.
I entered a /24 but the stored range differs
That is expected. Host bits are masked to the network address (203.0.113.55/24 → 203.0.113.0/24).
Does re-adding the same IP create a duplicate?
No. cidr is unique, so the existing entry is reactivated and its expiry/label updated (response reactivated: true). If the existing entry was seed, the source stays seed.
Why do auto entries appear / can I delete them?
srct_tx auto-registered an IP it judged clearly innocent, as a 30-day exemption. You can delete it, but if that IP is falsely flagged again it may be re-registered.
Do expired entries disappear automatically?
The row remains but drops out of the "active" counts once its expiry passes. Switch the status filter to expired/all to see them. Remove them fully with delete.
I exempted an IP but it is still being blocked
Check: (1) whether the normalized range actually covers the traffic IP (e.g. /32 vs /24), (2) the cidrList() cache (30s) and whether the collector script was regenerated, (3) whether a block record already exists in blacklist_info. Existing blocks must be released from the Blocklist.
Related
- Blocklist & Reputation — block-list management (the opposite of exemption)
- Auto-block — scheduler auto-blocking (whitelist/innocence veto)
- Threats — IDS threat triage, IP drilldown
- Rules & Thresholds — srct_tx/kts_tx constants + cidrList injection
- IP reputation — in-app AbuseIPDB lookup
- Audit log — auditing write operations
