Skip to content

Commit 228a493

Browse files
authored
Add allowed ip check (#605)
* Add allowed ip check * Disable allowed ip checks for system tests * Embed IPs file and change docs * Update validate.go * Add allowed for ipv6 * Trim possible empty IPs
1 parent 2924260 commit 228a493

File tree

4 files changed

+95
-22
lines changed

4 files changed

+95
-22
lines changed

docs/howto/ingest_geoip.md

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,7 @@
22

33
Elasticsearch provides default GeoIP databases that can be downloaded in runtime and which weights ~70 MB. This can be
44
a root cause of flakiness of package tests, so elastic-package embeds small samples of GeoIP databases, that can identify
5-
accurately only few ranges of IP addresses:
6-
7-
```
8-
1.128.3.4
9-
175.16.199.1
10-
216.160.83.57
11-
216.160.83.61
12-
67.43.156.12
13-
81.2.69.143
14-
81.2.69.144
15-
81.2.69.145
16-
81.2.69.193
17-
89.160.20.112
18-
89.160.20.156
19-
67.43.156.12
20-
67.43.156.13
21-
67.43.156.14
22-
67.43.156.15
23-
2a02:cf40:add:4002:91f2:a9b2:e09a:6fc6
24-
```
5+
accurately only few ranges of IP addresses included [here](../../internal/fields/_static/allowed_geo_ips.txt)
256

267
If you want the ingest pipeline to include a "geo" section in the event, feel free to use one of above IP addresses.
278
Embedded databases contain information about: cities, countries and ASNs.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
1.128.3.4
2+
175.16.199.1
3+
216.160.83.57
4+
216.160.83.61
5+
81.2.69.143
6+
81.2.69.144
7+
81.2.69.145
8+
81.2.69.193
9+
89.160.20.112
10+
89.160.20.156
11+
67.43.156.12
12+
67.43.156.13
13+
67.43.156.14
14+
67.43.156.15
15+
2a02:cf40:add:4002:91f2:a9b2:e09a:6fc6

internal/fields/validate.go

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
package fields
66

77
import (
8+
_ "embed"
89
"encoding/json"
910
"fmt"
11+
"net"
1012
"os"
1113
"path/filepath"
1214
"regexp"
@@ -31,6 +33,9 @@ type Validator struct {
3133
numericKeywordFields map[string]struct{}
3234

3335
disabledDependencyManagement bool
36+
37+
enabledAllowedIPCheck bool
38+
allowedIPs map[string]struct{}
3439
}
3540

3641
// ValidatorOption represents an optional flag that can be passed to CreateValidatorForDataStream.
@@ -64,6 +69,14 @@ func WithDisabledDependencyManagement() ValidatorOption {
6469
}
6570
}
6671

72+
// WithEnabledAllowedIPCheck configures the validator to perform check on the IP values against an allowed list.
73+
func WithEnabledAllowedIPCheck() ValidatorOption {
74+
return func(v *Validator) error {
75+
v.enabledAllowedIPCheck = true
76+
return nil
77+
}
78+
}
79+
6780
// CreateValidatorForDataStream function creates a validator for the data stream.
6881
func CreateValidatorForDataStream(dataStreamRootPath string, opts ...ValidatorOption) (v *Validator, err error) {
6982
v = new(Validator)
@@ -72,6 +85,9 @@ func CreateValidatorForDataStream(dataStreamRootPath string, opts ...ValidatorOp
7285
return nil, err
7386
}
7487
}
88+
89+
v.allowedIPs = initializeAllowedIPsList()
90+
7591
v.Schema, err = loadFieldsForDataStream(dataStreamRootPath)
7692
if err != nil {
7793
return nil, errors.Wrapf(err, "can't load fields for data stream (path: %s)", dataStreamRootPath)
@@ -99,6 +115,25 @@ func CreateValidatorForDataStream(dataStreamRootPath string, opts ...ValidatorOp
99115
return v, nil
100116
}
101117

118+
//go:embed _static/allowed_geo_ips.txt
119+
var allowedGeoIPs string
120+
121+
func initializeAllowedIPsList() map[string]struct{} {
122+
m := map[string]struct{}{
123+
"0.0.0.0": {}, "255.255.255.255": {},
124+
"0:0:0:0:0:0:0:0": {}, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff": {},
125+
}
126+
for _, ip := range strings.Split(allowedGeoIPs, "\n") {
127+
ip = strings.Trim(ip, " \n\t")
128+
if ip == "" {
129+
continue
130+
}
131+
m[ip] = struct{}{}
132+
}
133+
134+
return m
135+
}
136+
102137
func loadFieldsForDataStream(dataStreamRootPath string) ([]FieldDefinition, error) {
103138
fieldsDir := filepath.Join(dataStreamRootPath, "fields")
104139
files, err := filepath.Glob(filepath.Join(fieldsDir, "*.yml"))
@@ -306,7 +341,7 @@ func (v *Validator) parseElementValue(key string, definition FieldDefinition, va
306341
if err := ensurePatternMatches(key, valStr, definition.Pattern); err != nil {
307342
return err
308343
}
309-
case "date", "ip", "keyword", "text":
344+
case "date", "keyword", "text":
310345
var valStr string
311346
valStr, valid = val.(string)
312347
if !valid {
@@ -316,6 +351,20 @@ func (v *Validator) parseElementValue(key string, definition FieldDefinition, va
316351
if err := ensurePatternMatches(key, valStr, definition.Pattern); err != nil {
317352
return err
318353
}
354+
case "ip":
355+
var valStr string
356+
valStr, valid = val.(string)
357+
if !valid {
358+
break
359+
}
360+
361+
if err := ensurePatternMatches(key, valStr, definition.Pattern); err != nil {
362+
return err
363+
}
364+
365+
if v.enabledAllowedIPCheck && !v.isAllowedIPValue(valStr) {
366+
return fmt.Errorf("the IP %q is not one of the allowed test IPs", valStr)
367+
}
319368
case "float", "long", "double":
320369
_, valid = val.(float64)
321370
default:
@@ -328,6 +377,30 @@ func (v *Validator) parseElementValue(key string, definition FieldDefinition, va
328377
return nil
329378
}
330379

380+
// isAllowedIPValue checks if the provided IP is allowed for testing
381+
// The set of allowed IPs are:
382+
// - private IPs as described in RFC 1918 & RFC 4193
383+
// - public IPs allowed by MaxMind for testing
384+
// - 0.0.0.0 and 255.255.255.255 for IPv4
385+
// - 0:0:0:0:0:0:0:0 and ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff for IPv6
386+
func (v *Validator) isAllowedIPValue(s string) bool {
387+
if _, found := v.allowedIPs[s]; found {
388+
return true
389+
}
390+
391+
ip := net.ParseIP(s)
392+
if ip == nil {
393+
return false
394+
}
395+
396+
if ip.IsPrivate() || ip.IsLoopback() ||
397+
ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
398+
return true
399+
}
400+
401+
return false
402+
}
403+
331404
// ensureSingleElementValue extracts single entity from a potential array, which is a valid field representation
332405
// in Elasticsearch. For type assertion we need a single value.
333406
func ensureSingleElementValue(val interface{}) (interface{}, bool) {

internal/testrunner/runners/pipeline/runner.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,11 @@ func (r *runner) run() ([]testrunner.TestResult, error) {
131131

132132
tr.TimeElapsed = time.Since(startTime)
133133
fieldsValidator, err := fields.CreateValidatorForDataStream(dataStreamPath,
134-
fields.WithNumericKeywordFields(tc.config.NumericKeywordFields))
134+
fields.WithNumericKeywordFields(tc.config.NumericKeywordFields),
135+
// explicitly enabled for pipeline tests only
136+
// since system tests can have dynamic public IPs
137+
fields.WithEnabledAllowedIPCheck(),
138+
)
135139
if err != nil {
136140
return nil, errors.Wrapf(err, "creating fields validator for data stream failed (path: %s, test case file: %s)", dataStreamPath, testCaseFile)
137141
}

0 commit comments

Comments
 (0)