Gatekeeper inqaba-security/gatekeeper
v1.x

GeoIP Enrichment

GeoIP enrichment adds where to every event's who: ECS source.geo.* fields (country, city, coordinates) and source.as.organization.name (the network operator). Analysts see 203.0.113.9 (Oslo, NO) in Slack alerts, and can map, filter and alert on geography in the SIEM — impossible-travel detections, "admin login from a country we don't operate in", hosting-provider ASN filtering.

SIEM_GEOIP=true
SIEM_GEOIP_DRIVER=maxmind

Where enrichment happens (and why it's free)#

Lookups run at delivery time on the queue worker — never during the user's request. Results are cached per IP (default 24 h), including misses, and a failing resolver is backed off for 5 minutes. Failure semantics are strict: any resolver error degrades to an un-enriched event. GeoIP can never delay a request or lose an event.

Enriched fields:

{
    "source": {
        "ip": "203.0.113.9",
        "geo": {
            "country_iso_code": "NO",
            "country_name": "Norway",
            "city_name": "Oslo",
            "location": { "lat": 59.91, "lon": 10.75 }
        },
        "as": { "organization": { "name": "AS2119 Telenor" } }
    }
}

Private and reserved addresses (10.x, 192.168.x, loopback, ...) are skipped automatically.

Offline lookups against a local GeoLite2/GeoIP2 City database — microseconds per lookup, no network dependency, no third-party terms on your traffic.

composer require geoip2/geoip2
SIEM_GEOIP_DRIVER=maxmind
SIEM_GEOIP_MMDB=/var/lib/geoip/GeoLite2-City.mmdb

GeoLite2 databases are free with a MaxMind account. Keep the file fresh with their geoipupdate tool on a weekly cron — IP geolocation drifts constantly.

Driver: ipapi (zero dependencies)#

HTTP lookups against an ip-api.com-style JSON endpoint. No package, no database file — at the cost of a network call per uncached IP and the provider's terms of use (ip-api.com's free tier is non-commercial; the URL is configurable to any compatible endpoint, including their paid HTTPS one).

SIEM_GEOIP_DRIVER=ipapi
# optional: custom endpoint, {ip} is substituted
SIEM_GEOIP_URL="https://pro.ip-api.com/json/{ip}?key=...&fields=status,country,countryCode,city,lat,lon,as"

Because results are cached per IP and lookups run on the worker, even the HTTP driver adds no user-facing latency — a scanner probing you 500 times costs exactly one lookup.

Custom resolver#

Bind your own implementation (commercial geo API, internal IP-intelligence service, threat-intel enrichment):

use Inqaba\Gatekeeper\Geo\GeoIpResolver;
use Inqaba\Gatekeeper\Geo\GeoLocation;

$this->app->bind(GeoIpResolver::class, fn () => new class implements GeoIpResolver {
    public function resolve(string $ip): ?GeoLocation
    {
        $data = MyGeoService::lookup($ip);

        return new GeoLocation(
            countryIso: $data->country,
            countryName: $data->countryName,
            city: $data->city,
            lat: $data->lat,
            lon: $data->lon,
            organization: $data->asn,
        );
    }
});

Return null for unknown addresses; throw for infrastructure errors (the enricher catches, logs nothing sensitive, and moves on).

Configuration#

Key Env Default
geoip.enabled SIEM_GEOIP false
geoip.driver SIEM_GEOIP_DRIVER maxmind
geoip.cache_minutes 1440
geoip.maxmind.database SIEM_GEOIP_MMDB storage/app/geoip/GeoLite2-City.mmdb
geoip.ipapi.url SIEM_GEOIP_URL ip-api.com template
geoip.ipapi.timeout 3