55package fields
66
77import (
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.
6881func 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+
102137func 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.
333406func ensureSingleElementValue (val interface {}) (interface {}, bool ) {
0 commit comments