Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for the 'Domain' database to the geoip processor #108639

Merged
merged 6 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/changelog/108639.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
pr: 108639
summary: Add support for the 'Domain' database to the geoip processor
area: Ingest Node
type: enhancement
issues: []
highlight:
title: Add support for the 'Domain' database to the geoip processor
body: |-
Follow on to #107287 and #107377

Adds support for the ['GeoIP2
Domain'](https://dev.maxmind.com/geoip/docs/databases/domain) database
from MaxMind to the `geoip` processor.

The `geoip` processor will automatically download the [various
'GeoLite2'
databases](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data),
but the 'GeoIP2 Domain' database is not a 'GeoLite2' database -- it's a
commercial database available to those with a suitable license from
MaxMind.

The support that is being added for it in this PR is in line with the
support that we already have for MaxMind's 'GeoIP2 City' and 'GeoIP2
Country' databases -- that is, one would need to arrange their own
download management via some custom endpoint or otherwise arrange for
the relevant file(s) to be in the `$ES_CONFIG/ingest-geoip` directory on
the nodes of the cluster.
notable: true
1 change: 1 addition & 0 deletions docs/reference/ingest/processors/geoip.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ in `properties`.
* If the GeoIP2 Anonymous IP database is used, then the following fields may be added under the `target_field`: `ip`,
`hosting_provider`, `tor_exit_node`, `anonymous_vpn`, `anonymous`, `public_proxy`, and `residential_proxy`. The fields actually added
depend on what has been found and which properties were configured in `properties`.
* If the GeoIP2 Domain database is used, then the following fields may be added under the `target_field`: `ip`, and `domain`.
* If the GeoIP2 Enterprise database is used, then the following fields may be added under the `target_field`: `ip`,
`country_iso_code`, `country_name`, `continent_name`, `region_iso_code`, `region_name`, `city_name`, `timezone`, `location`, `asn`,
`organization_name`, `network`, `hosting_provider`, `tor_exit_node`, `anonymous_vpn`, `anonymous`, `public_proxy`, and `residential_proxy`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ enum Database {
Property.RESIDENTIAL_PROXY
)
),
Domain(Set.of(Property.IP, Property.DOMAIN), Set.of(Property.DOMAIN)),
Enterprise(
Set.of(
Property.IP,
Expand All @@ -94,7 +95,8 @@ enum Database {
Property.ANONYMOUS_VPN,
Property.ANONYMOUS,
Property.PUBLIC_PROXY,
Property.RESIDENTIAL_PROXY
Property.RESIDENTIAL_PROXY,
Property.DOMAIN
),
Set.of(
Property.COUNTRY_ISO_CODE,
Expand All @@ -111,6 +113,7 @@ enum Database {
private static final String COUNTRY_DB_SUFFIX = "-Country";
private static final String ASN_DB_SUFFIX = "-ASN";
private static final String ANONYMOUS_IP_DB_SUFFIX = "-Anonymous-IP";
private static final String DOMAIN_DB_SUFFIX = "-Domain";
private static final String ENTERPRISE_DB_SUFFIX = "-Enterprise";

/**
Expand All @@ -133,6 +136,8 @@ public static Database getDatabase(final String databaseType, final String datab
database = Database.Asn;
} else if (databaseType.endsWith(Database.ANONYMOUS_IP_DB_SUFFIX)) {
database = Database.AnonymousIp;
} else if (databaseType.endsWith(Database.DOMAIN_DB_SUFFIX)) {
database = Database.Domain;
} else if (databaseType.endsWith(Database.ENTERPRISE_DB_SUFFIX)) {
database = Database.Enterprise;
}
Expand Down Expand Up @@ -209,7 +214,8 @@ enum Property {
ANONYMOUS_VPN,
ANONYMOUS,
PUBLIC_PROXY,
RESIDENTIAL_PROXY;
RESIDENTIAL_PROXY,
DOMAIN;

/**
* Parses a string representation of a property into an actual Property instance. Not all properties that exist are
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.maxmind.geoip2.model.AsnResponse;
import com.maxmind.geoip2.model.CityResponse;
import com.maxmind.geoip2.model.CountryResponse;
import com.maxmind.geoip2.model.DomainResponse;
import com.maxmind.geoip2.model.EnterpriseResponse;

import org.apache.logging.log4j.LogManager;
Expand Down Expand Up @@ -177,6 +178,12 @@ public AnonymousIpResponse getAnonymousIp(InetAddress ipAddress) {
return getResponse(ipAddress, DatabaseReader::tryAnonymousIp);
}

@Nullable
@Override
public DomainResponse getDomain(InetAddress ipAddress) {
return getResponse(ipAddress, DatabaseReader::tryDomain);
}

@Nullable
@Override
public EnterpriseResponse getEnterprise(InetAddress ipAddress) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.maxmind.geoip2.model.AsnResponse;
import com.maxmind.geoip2.model.CityResponse;
import com.maxmind.geoip2.model.CountryResponse;
import com.maxmind.geoip2.model.DomainResponse;
import com.maxmind.geoip2.model.EnterpriseResponse;

import org.elasticsearch.core.Nullable;
Expand Down Expand Up @@ -58,6 +59,9 @@ public interface GeoIpDatabase {
@Nullable
AnonymousIpResponse getAnonymousIp(InetAddress ipAddress);

@Nullable
DomainResponse getDomain(InetAddress ipAddress);

@Nullable
EnterpriseResponse getEnterprise(InetAddress ipAddress);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.maxmind.geoip2.model.AsnResponse;
import com.maxmind.geoip2.model.CityResponse;
import com.maxmind.geoip2.model.CountryResponse;
import com.maxmind.geoip2.model.DomainResponse;
import com.maxmind.geoip2.model.EnterpriseResponse;
import com.maxmind.geoip2.record.City;
import com.maxmind.geoip2.record.Continent;
Expand Down Expand Up @@ -175,6 +176,7 @@ private Map<String, Object> getGeoData(GeoIpDatabase geoIpDatabase, String ip) t
case Country -> retrieveCountryGeoData(geoIpDatabase, ipAddress);
case Asn -> retrieveAsnGeoData(geoIpDatabase, ipAddress);
case AnonymousIp -> retrieveAnonymousIpGeoData(geoIpDatabase, ipAddress);
case Domain -> retrieveDomainGeoData(geoIpDatabase, ipAddress);
case Enterprise -> retrieveEnterpriseGeoData(geoIpDatabase, ipAddress);
};
}
Expand Down Expand Up @@ -384,6 +386,28 @@ private Map<String, Object> retrieveAnonymousIpGeoData(GeoIpDatabase geoIpDataba
return geoData;
}

private Map<String, Object> retrieveDomainGeoData(GeoIpDatabase geoIpDatabase, InetAddress ipAddress) {
DomainResponse response = geoIpDatabase.getDomain(ipAddress);
if (response == null) {
return Map.of();
}

String domain = response.getDomain();

Map<String, Object> geoData = new HashMap<>();
for (Property property : this.properties) {
switch (property) {
case IP -> geoData.put("ip", NetworkAddress.format(ipAddress));
case DOMAIN -> {
if (domain != null) {
geoData.put("domain", domain);
}
}
}
}
return geoData;
}

private Map<String, Object> retrieveEnterpriseGeoData(GeoIpDatabase geoIpDatabase, InetAddress ipAddress) {
EnterpriseResponse response = geoIpDatabase.getEnterprise(ipAddress);
if (response == null) {
Expand All @@ -407,6 +431,8 @@ private Map<String, Object> retrieveEnterpriseGeoData(GeoIpDatabase geoIpDatabas
boolean isPublicProxy = response.getTraits().isPublicProxy();
boolean isResidentialProxy = response.getTraits().isResidentialProxy();

String domain = response.getTraits().getDomain();

Map<String, Object> geoData = new HashMap<>();
for (Property property : this.properties) {
switch (property) {
Expand Down Expand Up @@ -500,6 +526,11 @@ private Map<String, Object> retrieveEnterpriseGeoData(GeoIpDatabase geoIpDatabas
case RESIDENTIAL_PROXY -> {
geoData.put("residential_proxy", isResidentialProxy);
}
case DOMAIN -> {
if (domain != null) {
geoData.put("domain", domain);
}
}
}
}
return geoData;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,36 @@ public void testAnonymmousIp() throws Exception {
assertThat(geoData.get("residential_proxy"), equalTo(true));
}

public void testDomain() throws Exception {
String ip = "69.219.64.2";
GeoIpProcessor processor = new GeoIpProcessor(
randomAlphaOfLength(10),
null,
"source_field",
loader("/GeoIP2-Domain-Test.mmdb"),
() -> true,
"target_field",
ALL_PROPERTIES,
false,
false,
"filename"
);

Map<String, Object> document = new HashMap<>();
document.put("source_field", ip);
IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document);
processor.execute(ingestDocument);

assertThat(ingestDocument.getSourceAndMetadata().get("source_field"), equalTo(ip));
@SuppressWarnings("unchecked")
Map<String, Object> geoData = (Map<String, Object>) ingestDocument.getSourceAndMetadata().get("target_field");
assertThat(geoData.size(), equalTo(2));
assertThat(geoData.get("ip"), equalTo(ip));
assertThat(geoData.get("domain"), equalTo("ameritech.net"));
}

public void testEnterprise() throws Exception {
String ip = "2.125.160.216";
String ip = "74.209.24.4";
GeoIpProcessor processor = new GeoIpProcessor(
randomAlphaOfLength(10),
null,
Expand All @@ -359,26 +387,29 @@ public void testEnterprise() throws Exception {
assertThat(ingestDocument.getSourceAndMetadata().get("source_field"), equalTo(ip));
@SuppressWarnings("unchecked")
Map<String, Object> geoData = (Map<String, Object>) ingestDocument.getSourceAndMetadata().get("target_field");
assertThat(geoData.size(), equalTo(16));
assertThat(geoData.size(), equalTo(19));
assertThat(geoData.get("ip"), equalTo(ip));
assertThat(geoData.get("country_iso_code"), equalTo("GB"));
assertThat(geoData.get("country_name"), equalTo("United Kingdom"));
assertThat(geoData.get("continent_name"), equalTo("Europe"));
assertThat(geoData.get("region_iso_code"), equalTo("GB-WBK"));
assertThat(geoData.get("region_name"), equalTo("West Berkshire"));
assertThat(geoData.get("city_name"), equalTo("Boxford"));
assertThat(geoData.get("timezone"), equalTo("Europe/London"));
assertThat(geoData.get("country_iso_code"), equalTo("US"));
assertThat(geoData.get("country_name"), equalTo("United States"));
assertThat(geoData.get("continent_name"), equalTo("North America"));
assertThat(geoData.get("region_iso_code"), equalTo("US-NY"));
assertThat(geoData.get("region_name"), equalTo("New York"));
assertThat(geoData.get("city_name"), equalTo("Chatham"));
assertThat(geoData.get("timezone"), equalTo("America/New_York"));
Map<String, Object> location = new HashMap<>();
location.put("lat", 51.75);
location.put("lon", -1.25);
location.put("lat", 42.3478);
location.put("lon", -73.5549);
assertThat(geoData.get("location"), equalTo(location));
assertThat(geoData.get("network"), equalTo("2.125.160.216/29"));
assertThat(geoData.get("asn"), equalTo(14671L));
assertThat(geoData.get("organization_name"), equalTo("FairPoint Communications"));
assertThat(geoData.get("network"), equalTo("74.209.16.0/20"));
assertThat(geoData.get("hosting_provider"), equalTo(false));
assertThat(geoData.get("tor_exit_node"), equalTo(false));
assertThat(geoData.get("anonymous_vpn"), equalTo(false));
assertThat(geoData.get("anonymous"), equalTo(false));
assertThat(geoData.get("public_proxy"), equalTo(false));
assertThat(geoData.get("residential_proxy"), equalTo(false));
assertThat(geoData.get("domain"), equalTo("frpt.net"));
}

public void testAddressIsNotInTheDatabase() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ public class MaxMindSupportTests extends ESTestCase {
"traits.userType"
);

private static final Set<String> DOMAIN_SUPPORTED_FIELDS = Set.of("domain");
private static final Set<String> DOMAIN_UNSUPPORTED_FIELDS = Set.of("ipAddress", "network");

private static final Set<String> ENTERPRISE_SUPPORTED_FIELDS = Set.of(
"city.name",
"continent.name",
Expand All @@ -215,6 +218,7 @@ public class MaxMindSupportTests extends ESTestCase {
"traits.anonymousVpn",
"traits.autonomousSystemNumber",
"traits.autonomousSystemOrganization",
"traits.domain",
"traits.hostingProvider",
"traits.network",
"traits.publicProxy",
Expand Down Expand Up @@ -268,7 +272,6 @@ public class MaxMindSupportTests extends ESTestCase {
"traits.anonymousProxy",
"traits.anycast",
"traits.connectionType",
"traits.domain",
"traits.ipAddress",
"traits.isp",
"traits.legitimateProxy",
Expand All @@ -290,6 +293,8 @@ public class MaxMindSupportTests extends ESTestCase {
CITY_SUPPORTED_FIELDS,
Database.Country,
COUNTRY_SUPPORTED_FIELDS,
Database.Domain,
DOMAIN_SUPPORTED_FIELDS,
Database.Enterprise,
ENTERPRISE_SUPPORTED_FIELDS
);
Expand All @@ -302,6 +307,8 @@ public class MaxMindSupportTests extends ESTestCase {
CITY_UNSUPPORTED_FIELDS,
Database.Country,
COUNTRY_UNSUPPORTED_FIELDS,
Database.Domain,
DOMAIN_UNSUPPORTED_FIELDS,
Database.Enterprise,
ENTERPRISE_UNSUPPORTED_FIELDS
);
Expand All @@ -314,13 +321,14 @@ public class MaxMindSupportTests extends ESTestCase {
CityResponse.class,
Database.Country,
CountryResponse.class,
Database.Domain,
DomainResponse.class,
Database.Enterprise,
EnterpriseResponse.class
);

private static final Set<Class<? extends AbstractResponse>> KNOWN_UNSUPPORTED_RESPONSE_CLASSES = Set.of(
ConnectionTypeResponse.class,
DomainResponse.class,
IspResponse.class,
IpRiskResponse.class
);
Expand Down
Binary file not shown.