Skip to content

Commit feeb56b

Browse files
authored
Merge pull request #206 from yaauie/ecs-region_iso_code
ECS: support composite region_iso_code
2 parents bca992f + 8e96172 commit feeb56b

File tree

6 files changed

+100
-27
lines changed

6 files changed

+100
-27
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 7.2.11
2+
- Improved compatibility with the Elastic Common Schema [#206](https://github.com/logstash-plugins/logstash-filter-geoip/pull/206)
3+
- Added support for ECS's composite `region_iso_code` (`US-WA`), which _replaces_ the non-ECS `region_code` (`WA`) as a default field with City databases. To get the stand-alone `region_code` in ECS mode, you must include it in the `fields` directive.
4+
- [DOC] Improve ECS-related documentation
5+
16
## 7.2.10
27
- [DOC] Air-gapped environment requires both ASN and City databases [#204](https://github.com/logstash-plugins/logstash-filter-geoip/pull/204)
38

docs/index.asciidoc

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,57 @@ Example response:
169169
}
170170
--------------------------------------------------
171171

172+
[id="plugins-{type}s-{plugin}-field-mapping"]
173+
==== Field mapping
174+
175+
When this plugin is run with <<plugins-{type}s-{plugin}-ecs_compatibility>> disabled, the MaxMind DB's fields are added directly to the <<plugins-{type}s-{plugin}-target>>.
176+
When ECS compatibility is enabled, the fields are structured to fit into an ECS shape.
177+
178+
[cols="3,5,3"]
179+
|===========================
180+
| Database Field Name | ECS Field | Example
181+
182+
| `ip` | `[ip]` | `12.34.56.78`
183+
184+
| `city_name` | `[geo][city_name]` | `Seattle`
185+
| `country_name` | `[geo][country_name]` | `United States`
186+
| `continent_code` | `[geo][continent_code]` | `NA`
187+
| `continent_name` | `[geo][continent_name]` | `North America`
188+
| `country_code2` | `[geo][country_iso_code]` | `US`
189+
| `country_code3` | _N/A_ | `US`
190+
191+
_maintained for legacy
192+
support, but populated
193+
with 2-character country
194+
code_
195+
196+
| `postal_code` | `[geo][postal_code]` | `98106`
197+
| `region_name` | `[geo][region_name]` | `Washington`
198+
| `region_code` | `[geo][region_code]` | `WA`
199+
| `region_iso_code`* | `[geo][region_iso_code]` | `US-WA`
200+
| `timezone` | `[geo][timezone]` | `America/Los_Angeles`
201+
| `location`* | `[geo][location]` | `{"lat": 47.6062, "lon": -122.3321}"`
202+
| `latitude` | `[geo][location][lat]` | `47.6062`
203+
| `longitude` | `[geo][location][lon]` | `-122.3321`
204+
205+
| `domain` | `[domain]` | `example.com`
206+
207+
| `asn` | `[as][number]` | `98765`
208+
| `as_org` | `[as][organization][name]` | `Elastic, NV`
209+
210+
| `isp` | `[mmdb][isp]` | `InterLink Supra LLC`
211+
| `dma_code` | `[mmdb][dma_code]` | `819`
212+
| `organization` | `[mmdb][organization]` | `Elastic, NV`
213+
|===========================
214+
215+
NOTE: `*` indicates a composite field, which is only populated if GeoIP lookup result contains all components.
216+
172217
==== Details
173218

174-
A `[geoip][location]` field is created if
175-
the GeoIP lookup returns a latitude and longitude. The field is stored in
176-
http://geojson.org/geojson-spec.html[GeoJSON] format. Additionally,
177-
the default Elasticsearch template provided with the
178-
{logstash-ref}/plugins-outputs-elasticsearch.html[elasticsearch output] maps
179-
the `[geoip][location]` field to an {ref}/geo-point.html[Elasticsearch Geo_point datatype].
219+
When using a City database, the enrichment is aborted if no latitude/longitude pair is available.
220+
221+
The `location` field combines the latitude and longitude into a structure called https://datatracker.ietf.org/doc/html/rfc7946[GeoJSON].
222+
When you are using a default <<plugins-{type}s-{plugin}-target>>, the templates provided by the {logstash-ref}/plugins-outputs-elasticsearch.html[elasticsearch output] map the field to an {ref}/geo-point.html[Elasticsearch Geo_point datatype].
180223

181224
As this field is a `geo_point` _and_ it is still valid GeoJSON, you get
182225
the awesomeness of Elasticsearch's geospatial query, facet and filter functions
@@ -242,16 +285,16 @@ number of cache misses and waste memory.
242285
===== `database`
243286

244287
* Value type is <<path,path>>
245-
* If not specified, the database defaults to the GeoLite2 City database that ships with Logstash.
288+
* If not specified, the database defaults to the `GeoLite2 City` database that ships with Logstash.
246289

247-
The path to MaxMind's database file that Logstash should use. The default database is GeoLite2-City.
248-
GeoLite2-City, GeoLite2-Country, GeoLite2-ASN are the free databases from MaxMind that are supported.
249-
GeoIP2-City, GeoIP2-ISP, GeoIP2-Country are the commercial databases from MaxMind that are supported.
290+
The path to MaxMind's database file that Logstash should use.
291+
The default database is `GeoLite2-City`.
292+
This plugin supports several free databases (`GeoLite2-City`, `GeoLite2-Country`, `GeoLite2-ASN`)
293+
and a selection of commercially-licensed databases (`GeoIP2-City`, `GeoIP2-ISP`, `GeoIP2-Country`).
250294

251-
Database auto-update applies to default distribution. When `database` points to user's database path,
252-
auto-update will be disabled.
253-
See
254-
<<plugins-{type}s-{plugin}-database_license,Database License>> for more information.
295+
Database auto-update applies to the default distribution.
296+
When `database` points to user's database path, auto-update is disabled.
297+
See <<plugins-{type}s-{plugin}-database_license,Database License>> for more information.
255298

256299
[id="plugins-{type}s-{plugin}-default_database_type"]
257300
===== `default_database_type`
@@ -270,21 +313,18 @@ This plugin now includes both the GeoLite2-City and GeoLite2-ASN databases. If
270313

271314
An array of geoip fields to be included in the event.
272315

273-
Possible fields depend on the database type. By default, all geoip fields
274-
are included in the event.
316+
Possible fields depend on the database type.
317+
By default, all geoip fields from the relevant database are included in the event.
275318

276-
For the built-in GeoLite2 City database, the following are available:
277-
`city_name`, `continent_code`, `country_code2`, `country_code3`, `country_name`,
278-
`dma_code`, `ip`, `latitude`, `location`, `longitude`, `postal_code`, `region_code`,
279-
`region_name` and `timezone`.
319+
For a complete list of available fields and how they map to an event's structure, see <<plugins-{type}s-{plugin}-field-mapping,field mapping>>.
280320

281321
[id="plugins-{type}s-{plugin}-ecs_compatibility"]
282322
===== `ecs_compatibility`
283323

284324
* Value type is <<string,string>>
285325
* Supported values are:
286326
** `disabled`: unstructured geo data added at root level
287-
** `v1`, `v8`: uses fields that are compatible with Elastic Common Schema (for example, `[client][geo][country_name]`)
327+
** `v1`, `v8`: use fields that are compatible with Elastic Common Schema. Example: `[client][geo][country_name]`. See <<plugins-{type}s-{plugin}-field-mapping,field mapping>> for more info.
288328
* Default value depends on which version of Logstash is running:
289329
** When Logstash provides a `pipeline.ecs_compatibility` setting, its value is used as the default
290330
** Otherwise, the default value is `disabled`.

logstash-filter-geoip.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Gem::Specification.new do |s|
22

33
s.name = 'logstash-filter-geoip'
4-
s.version = '7.2.10'
4+
s.version = '7.2.11'
55
s.licenses = ['Apache License (2.0)']
66
s.summary = "Adds geographical information about an IP address"
77
s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"

spec/filters/geoip_ecs_spec.rb

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
end
2828

2929
context "with city database" do
30+
# example.com, has been static for 10+ years
31+
# and has city-level details
32+
let(:ip) { "93.184.216.34" }
33+
3034
let(:options) { common_options }
3135

3236
it "should return geo in target" do
@@ -36,15 +40,23 @@
3640
expect( event.get ecs_select[disabled: "[#{target}][country_code2]", v1: "[#{target}][geo][country_iso_code]"] ).to eq 'US'
3741
expect( event.get ecs_select[disabled: "[#{target}][country_name]", v1: "[#{target}][geo][country_name]"] ).to eq 'United States'
3842
expect( event.get ecs_select[disabled: "[#{target}][continent_code]", v1: "[#{target}][geo][continent_code]"] ).to eq 'NA'
39-
expect( event.get ecs_select[disabled: "[#{target}][location][lat]", v1: "[#{target}][geo][location][lat]"] ).to eq 37.751
40-
expect( event.get ecs_select[disabled: "[#{target}][location][lon]", v1: "[#{target}][geo][location][lon]"] ).to eq -97.822
43+
expect( event.get ecs_select[disabled: "[#{target}][location][lat]", v1: "[#{target}][geo][location][lat]"] ).to eq 42.1596
44+
expect( event.get ecs_select[disabled: "[#{target}][location][lon]", v1: "[#{target}][geo][location][lon]"] ).to eq -70.8217
45+
expect( event.get ecs_select[disabled: "[#{target}][city_name]", v1: "[#{target}][geo][city_name]"] ).to eq 'Norwell'
46+
expect( event.get ecs_select[disabled: "[#{target}][dma_code]", v1: "[#{target}][mmdb][dma_code]"] ).to eq 506
47+
expect( event.get ecs_select[disabled: "[#{target}][region_name]", v1: "[#{target}][geo][region_name]"] ).to eq 'Massachusetts'
4148

4249
if ecs_select.active_mode == :disabled
4350
expect( event.get "[#{target}][country_code3]" ).to eq 'US'
51+
expect( event.get "[#{target}][region_code]" ).to eq 'MA'
52+
expect( event.get "[#{target}][region_iso_code]" ).to be_nil
4453
else
4554
expect( event.get "[#{target}][geo][country_code3]" ).to be_nil
4655
expect( event.get "[#{target}][country_code3]" ).to be_nil
56+
expect( event.get "[#{target}][geo][region_iso_code]" ).to eq 'US-MA'
57+
expect( event.get "[#{target}][region_code]" ).to be_nil
4758
end
59+
puts event.to_hash.inspect
4860
end
4961
end
5062

src/main/java/org/logstash/filters/geoip/Fields.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ enum Fields {
3939
DMA_CODE("mmdb.dma_code", "dma_code"),
4040
REGION_NAME("geo.region_name", "region_name"),
4141
REGION_CODE("geo.region_code", "region_code"),
42+
REGION_ISO_CODE("geo.region_iso_code", "region_iso_code"),
4243
TIMEZONE("geo.timezone", "timezone"),
4344
LOCATION("geo.location", "location"),
4445
LATITUDE("geo.location.lat", "latitude"),
@@ -96,6 +97,14 @@ public String getFieldReferenceECSv1() {
9697
Fields.COUNTRY_CODE3, Fields.IP, Fields.POSTAL_CODE, Fields.DMA_CODE, Fields.REGION_NAME,
9798
Fields.REGION_CODE, Fields.TIMEZONE, Fields.LOCATION, Fields.LATITUDE, Fields.LONGITUDE);
9899

100+
// When ECS is enabled, the composite REGION_ISO_CODE field is preferred to separate REGION_CODE
101+
static final EnumSet<Fields> DEFAULT_ECS_CITY_FIELDS;
102+
static {
103+
DEFAULT_ECS_CITY_FIELDS = EnumSet.copyOf(DEFAULT_CITY_FIELDS);
104+
DEFAULT_ECS_CITY_FIELDS.remove(REGION_CODE);
105+
DEFAULT_ECS_CITY_FIELDS.add(REGION_ISO_CODE);
106+
}
107+
99108
static final EnumSet<Fields> DEFAULT_COUNTRY_FIELDS = EnumSet.of(Fields.IP, Fields.COUNTRY_CODE2,
100109
Fields.IP, Fields.COUNTRY_NAME, Fields.CONTINENT_NAME);
101110

src/main/java/org/logstash/filters/geoip/GeoIPFilter.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public GeoIPFilter(String sourceField, String targetField, List<String> fields,
9191
} catch (IOException e) {
9292
throw new IllegalArgumentException("The database provided was not found in the path", e);
9393
}
94-
this.desiredFields = createDesiredFields(fields);
94+
this.desiredFields = createDesiredFields(fields, !ecsCompatibility.equals("disabled"));
9595
}
9696

9797
public static boolean isDatabaseValid(String databasePath) {
@@ -107,7 +107,7 @@ public static boolean isDatabaseValid(String databasePath) {
107107
return false;
108108
}
109109

110-
private Set<Fields> createDesiredFields(List<String> fields) {
110+
private Set<Fields> createDesiredFields(List<String> fields, final boolean ecsCompatibilityEnabled) {
111111
Set<Fields> desiredFields = EnumSet.noneOf(Fields.class);
112112
if (fields == null || fields.isEmpty()) {
113113
switch (databaseReader.getMetadata().getDatabaseType()) {
@@ -118,7 +118,7 @@ private Set<Fields> createDesiredFields(List<String> fields) {
118118
case CITY_EUROPE_DB_TYPE:
119119
case CITY_NORTH_AMERICA_DB_TYPE:
120120
case CITY_SOUTH_AMERICA_DB_TYPE:
121-
desiredFields = Fields.DEFAULT_CITY_FIELDS;
121+
desiredFields = ecsCompatibilityEnabled ? Fields.DEFAULT_ECS_CITY_FIELDS : Fields.DEFAULT_CITY_FIELDS;
122122
break;
123123
case COUNTRY_LITE_DB_TYPE:
124124
case COUNTRY_DB_TYPE:
@@ -311,6 +311,13 @@ private Map<Fields,Object> retrieveCityGeoData(InetAddress ipAddress) throws Geo
311311
geoData.put(Fields.REGION_CODE, subdivisionCode);
312312
}
313313
break;
314+
case REGION_ISO_CODE:
315+
String countryCodeForRegion = country.getIsoCode();
316+
String regionCode2 = subdivision.getIsoCode();
317+
if (countryCodeForRegion != null && regionCode2 != null) {
318+
geoData.put(Fields.REGION_ISO_CODE, String.format("%s-%s", countryCodeForRegion, regionCode2));
319+
}
320+
break;
314321
case TIMEZONE:
315322
String locationTimeZone = location.getTimeZone();
316323
if (locationTimeZone != null) {

0 commit comments

Comments
 (0)