x-pack/filebeat/input/internal/private/private.go (331 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. // Package private implements field redaction in maps and structs. package private import ( "fmt" "reflect" "slices" "strings" "unsafe" ) const tooDeep = 100 var privateKey = reflect.ValueOf("private") // Redact returns a copy of val with any fields or map elements that have been // marked as private removed. Fields can be marked as private by including a // sibling string- or []string-valued field or element with the name of the // private field. The names of fields are interpreted through the tag parameter // if present. For example if tag is "json", the `json:"<name>"` name would be // used, falling back to the field name if not present. The tag parameter is // ignored for map values. // // The global parameter indicates a set of dot-separated paths to redact. Paths // originate at the root of val. If global is used, the resultin redaction is on // the union of the fields redacted with tags and the fields redacted with the // global paths. // // If a field has a `private:...` tag, its tag value will also be used to // determine the list of private fields. If the private tag is empty, // `private:""`, the fields with the tag will be marked as private. Otherwise // the comma-separated list of names with be used. The list may refer to its // own field. func Redact[T any](val T, tag string, global []string, replace ...Replacer) (redacted T, err error) { reps, err := compileReplacers(replace) if err != nil { return redacted, err } defer func() { switch r := recover().(type) { case nil: return case cycle: // Make the returned type informative in all cases. // If Redact[any](v) is called and we use the zero // value, we would return a nil any, which is less // informative. redacted = reflect.New(reflect.TypeOf(val)).Elem().Interface().(T) err = r default: panic(r) } }() rv := reflect.ValueOf(val) switch rv.Kind() { case reflect.Map, reflect.Pointer, reflect.Struct: return redact(rv, reps, tag, slices.Clone(global), 0, make(map[any]int)).Interface().(T), nil default: return val, nil } } // Replacer is a function that will return a redaction replacement // for the provided type. It must be a func(T) T. type Replacer any // NewStringReplacer returns a string Replacer that returns s. func NewStringReplacer(s string) Replacer { return func(string) string { return s } } // NewBytesReplacer returns a []byte Replacer that returns the bytes // representation of s. func NewBytesReplacer(s string) Replacer { return func([]byte) []byte { return []byte(s) } } type replacers map[reflect.Type]func(reflect.Value) reflect.Value func compileReplacers(replace []Replacer) (replacers, error) { reps := make(replacers) for _, r := range replace { rv := reflect.ValueOf(r) rt := rv.Type() if rt.Kind() != reflect.Func { return nil, fmt.Errorf("replacer is not a function: %T", r) } if n := rt.NumIn(); n != 1 { return nil, fmt.Errorf("incorrect number of arguments for replacer: %d != 1", n) } if n := rt.NumOut(); n != 1 { return nil, fmt.Errorf("incorrect number of return values from replacer: %d != 1", n) } in, out := rt.In(0), rt.Out(0) if in != out { return nil, fmt.Errorf("replacer does not preserve type: fn(%s) %s", in, out) } if _, exists := reps[in]; exists { return nil, fmt.Errorf("multiple replacers for %s", in) } reps[in] = func(v reflect.Value) reflect.Value { return rv.Call([]reflect.Value{v})[0] } } if len(reps) == 0 { reps = nil } return reps, nil } func redact(v reflect.Value, reps replacers, tag string, global []string, depth int, seen map[any]int) reflect.Value { switch v.Kind() { case reflect.Pointer: if v.IsNil() { return v } if depth > tooDeep { ident := v.Interface() if last, ok := seen[ident]; ok && last < depth { panic(cycle{v.Type()}) } seen[ident] = depth defer delete(seen, ident) } return redact(v.Elem(), reps, tag, global, depth+1, seen).Addr() case reflect.Interface: if v.IsNil() { return v } return redact(v.Elem(), reps, tag, global, depth+1, seen) case reflect.Array: if v.Len() == 0 { return v } r := reflect.New(v.Type()).Elem() for i := 0; i < v.Len(); i++ { r.Index(i).Set(redact(v.Index(i), reps, tag, global, depth+1, seen)) } return r case reflect.Slice: if v.Len() == 0 { return v } if depth > tooDeep { ident := struct { data unsafe.Pointer len int }{ v.UnsafePointer(), v.Len(), } if last, ok := seen[ident]; ok && last < depth { panic(cycle{v.Type()}) } seen[ident] = depth defer delete(seen, ident) } r := reflect.MakeSlice(v.Type(), v.Len(), v.Cap()) for i := 0; i < v.Len(); i++ { r.Index(i).Set(redact(v.Index(i), reps, tag, global, depth+1, seen)) } return r case reflect.Map: if v.IsNil() { return v } if depth > tooDeep { ident := v.UnsafePointer() if last, ok := seen[ident]; ok && last < depth { panic(cycle{v.Type()}) } seen[ident] = depth defer delete(seen, ident) } private := nextStep(global) if privateKey.CanConvert(v.Type().Key()) { p := v.MapIndex(privateKey.Convert(v.Type().Key())) if p.IsValid() && p.CanInterface() { switch p := p.Interface().(type) { case string: private = append(private, p) case []string: private = append(private, p...) case []any: for _, s := range p { private = append(private, fmt.Sprint(s)) } } } } r := reflect.MakeMap(v.Type()) it := v.MapRange() for it.Next() { name := it.Key().String() if slices.Contains(private, name) { v := replaceNestedWithin(it.Value(), reps) if v.IsValid() { r.SetMapIndex(it.Key(), v) } continue } r.SetMapIndex(it.Key(), redact(it.Value(), reps, tag, nextPath(name, global), depth+1, seen)) } return r case reflect.Struct: private := nextStep(global) rt := v.Type() names := make([]string, rt.NumField()) for i := range names { f := rt.Field(i) // Look for `private:` tags. p, ok := f.Tag.Lookup("private") if ok { if p != "" { private = append(private, strings.Split(p, ",")...) } else { if tag == "" { names[i] = f.Name private = append(private, f.Name) } else { p = f.Tag.Get(tag) if p != "" { name, _, _ := strings.Cut(p, ",") names[i] = name private = append(private, name) } } } } // Look after Private fields if we are not using a tag. if tag == "" { names[i] = f.Name if f.Name == "Private" { switch p := v.Field(i).Interface().(type) { case string: private = append(private, p) case []string: private = append(private, p...) } } continue } // If we are using a tag, look for `tag:"<private>"` // falling back to fields named Private if no tag is // present. p = f.Tag.Get(tag) var name string if p == "" { name = f.Name } else { name, _, _ = strings.Cut(p, ",") } names[i] = name if name == "private" { switch p := v.Field(i).Interface().(type) { case string: private = append(private, p) case []string: private = append(private, p...) } } } r := reflect.New(v.Type()).Elem() for i := 0; i < v.NumField(); i++ { f := v.Field(i) if f.IsZero() || !rt.Field(i).IsExported() { continue } if slices.Contains(private, names[i]) { v := replaceNestedWithin(f, reps) if v.IsValid() { r.Field(i).Set(v) } continue } if r.Field(i).CanSet() { r.Field(i).Set(redact(f, reps, tag, nextPath(names[i], global), depth+1, seen)) } } return r } return v } // replaceNestedWithin replaces deeply nested values in pointer, interface and // array/slice chains. If a replacement is not made an invalid reflect.Value // is returned. If elements are not replaced by a replacer, it is set to the // zero value for the type. func replaceNestedWithin(v reflect.Value, reps replacers) reflect.Value { if len(reps) == 0 || !v.IsValid() { // No replacer, or an invalid value, so fall back to removal. return reflect.Value{} } if rep, ok := reps[v.Type()]; ok { return rep(v) } switch v.Kind() { case reflect.Pointer: r := replaceNestedWithin(v.Elem(), reps) if !r.IsValid() { return r } return r.Addr() case reflect.Interface: r := replaceNestedWithin(v.Elem(), reps) if !r.IsValid() { return r } i := reflect.New(v.Type()).Elem() i.Set(r) return i case reflect.Array: a := reflect.New(v.Type()).Elem() wasSet := false for i := 0; i < v.Len(); i++ { r := replaceNestedWithin(v.Index(i), reps) if r.IsValid() { wasSet = true a.Index(i).Set(r) } } if !wasSet { return reflect.Value{} } return a case reflect.Slice: s := reflect.MakeSlice(v.Type(), v.Len(), v.Cap()) wasSet := false for i := 0; i < v.Len(); i++ { r := replaceNestedWithin(v.Index(i), reps) if r.IsValid() { wasSet = true s.Index(i).Set(r) } } if !wasSet { return reflect.Value{} } return s default: // Could not catch, fall back to removal. return reflect.Value{} } } func nextStep(global []string) (private []string) { if len(global) == 0 { return nil } private = make([]string, 0, len(global)) for _, s := range global { key, _, more := strings.Cut(s, ".") if !more { private = append(private, key) } } return private } func nextPath(step string, global []string) []string { if len(global) == 0 { return nil } step += "." next := make([]string, 0, len(global)) for _, s := range global { if !strings.HasPrefix(s, step) { continue } next = append(next, s[len(step):]) } return next } type cycle struct { typ reflect.Type } func (e cycle) Error() string { return fmt.Sprintf("cycle including %s", e.typ) }