mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-24 22:47:14 +02:00
194 lines
4.2 KiB
Go
194 lines
4.2 KiB
Go
package config
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
|
"github.com/mitchellh/mapstructure"
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
// A StringSlice is a slice of strings.
|
|
type StringSlice []string
|
|
|
|
// NewStringSlice creatse a new StringSlice.
|
|
func NewStringSlice(values ...string) StringSlice {
|
|
return StringSlice(values)
|
|
}
|
|
|
|
const (
|
|
array = iota
|
|
arrayValue
|
|
object
|
|
objectKey
|
|
objectValue
|
|
)
|
|
|
|
// UnmarshalJSON unmarshals a JSON document into the string slice.
|
|
func (slc *StringSlice) UnmarshalJSON(data []byte) error {
|
|
typeStack := []int{array}
|
|
stateStack := []int{arrayValue}
|
|
|
|
var vals []string
|
|
dec := json.NewDecoder(bytes.NewReader(data))
|
|
for {
|
|
token, err := dec.Token()
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
if delim, ok := token.(json.Delim); ok {
|
|
switch delim {
|
|
case '[':
|
|
typeStack = append(typeStack, array)
|
|
stateStack = append(stateStack, arrayValue)
|
|
case '{':
|
|
typeStack = append(typeStack, object)
|
|
stateStack = append(stateStack, objectKey)
|
|
case ']', '}':
|
|
typeStack = typeStack[:len(typeStack)-1]
|
|
stateStack = stateStack[:len(stateStack)-1]
|
|
}
|
|
continue
|
|
}
|
|
|
|
switch stateStack[len(stateStack)-1] {
|
|
case objectKey:
|
|
stateStack[len(stateStack)-1] = objectValue
|
|
case objectValue:
|
|
stateStack[len(stateStack)-1] = objectKey
|
|
fallthrough
|
|
default:
|
|
switch t := token.(type) {
|
|
case bool:
|
|
vals = append(vals, fmt.Sprint(t))
|
|
case float64:
|
|
vals = append(vals, fmt.Sprint(t))
|
|
case json.Number:
|
|
vals = append(vals, fmt.Sprint(t))
|
|
case string:
|
|
vals = append(vals, t)
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
*slc = StringSlice(vals)
|
|
return nil
|
|
}
|
|
|
|
// UnmarshalYAML unmarshals a YAML document into the string slice. UnmarshalJSON is
|
|
// reused as the actual implementation.
|
|
func (slc *StringSlice) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
var i interface{}
|
|
err := unmarshal(&i)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bs, err := json.Marshal(i)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return slc.UnmarshalJSON(bs)
|
|
}
|
|
|
|
// DecodeOptionsHookFunc returns a decode hook that will attempt to convert any type to a StringSlice.
|
|
func DecodeOptionsHookFunc() mapstructure.DecodeHookFunc {
|
|
return func(f, t reflect.Type, data interface{}) (interface{}, error) {
|
|
if t != reflect.TypeOf(Options{}) {
|
|
return data, nil
|
|
}
|
|
|
|
m, ok := data.(map[string]interface{})
|
|
if !ok {
|
|
return data, nil
|
|
}
|
|
|
|
ps, ok := m[policyKey].([]interface{})
|
|
if !ok {
|
|
return data, nil
|
|
}
|
|
|
|
for _, p := range ps {
|
|
pm, ok := p.(map[interface{}]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
raw, ok := pm[healthCheckKey]
|
|
if ok {
|
|
hc := new(envoy_config_core_v3.HealthCheck)
|
|
if err := parseJSONPB(raw, hc); err != nil {
|
|
return nil, fmt.Errorf("%s: %w", healthCheckKey, err)
|
|
}
|
|
pm[healthCheckKey] = hc
|
|
}
|
|
rawTo, ok := pm[toKey]
|
|
if !ok {
|
|
continue
|
|
}
|
|
rawBS, err := json.Marshal(rawTo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var slc StringSlice
|
|
err = json.Unmarshal(rawBS, &slc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pm[toKey] = slc
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
// parseJSONPB takes an intermediate representation and parses it using protobuf parser
|
|
// that correctly handles oneof and other data types
|
|
func parseJSONPB(raw interface{}, dst proto.Message) error {
|
|
ms, err := serializable(raw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data, err := json.Marshal(ms)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return protojson.Unmarshal(data, dst)
|
|
}
|
|
|
|
// serializable converts mapstructure nested map into map[string]interface{} that is serializable to JSON
|
|
func serializable(in interface{}) (interface{}, error) {
|
|
switch typed := in.(type) {
|
|
case map[interface{}]interface{}:
|
|
m := make(map[string]interface{})
|
|
for k, v := range typed {
|
|
kstr, ok := k.(string)
|
|
if !ok {
|
|
return nil, errKeysMustBeStrings
|
|
}
|
|
val, err := serializable(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m[kstr] = val
|
|
}
|
|
return m, nil
|
|
case []interface{}:
|
|
out := make([]interface{}, 0, len(typed))
|
|
for _, elem := range typed {
|
|
val, err := serializable(elem)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, val)
|
|
}
|
|
return out, nil
|
|
default:
|
|
return in, nil
|
|
}
|
|
}
|